deepline 0.3.6 → 0.3.8
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/bundling-sources/sdk/src/play.ts +4 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +144 -37
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/secret-capability.ts +163 -14
- package/dist/bundling-sources/shared_libs/play-runtime/secret-redaction.ts +44 -2
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +44 -15
- package/dist/bundling-sources/shared_libs/plays/docflow.ts +68 -103
- package/dist/bundling-sources/shared_libs/plays/secret-guardrails.ts +137 -1
- package/dist/bundling-sources/shared_libs/security/safe-fetch.ts +25 -7
- package/dist/cli/index.js +153 -9
- package/dist/cli/index.mjs +153 -9
- package/dist/{compiler-manifest-CdgJeOr2.d.mts → compiler-manifest-YIjoJo8y.d.mts} +27 -16
- package/dist/{compiler-manifest-CdgJeOr2.d.ts → compiler-manifest-YIjoJo8y.d.ts} +27 -16
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +30 -3
- package/dist/index.mjs +30 -3
- package/dist/install-integrity.json +2 -2
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/plays/bundle-play-file.mjs +169 -78
- package/package.json +1 -1
|
@@ -152,6 +152,8 @@ import type {
|
|
|
152
152
|
PlaySecretAuthInput,
|
|
153
153
|
PlaySecretAwareRequestInit,
|
|
154
154
|
PlaySecretHandle,
|
|
155
|
+
PlaySecretPromise,
|
|
156
|
+
PlaySecretValue,
|
|
155
157
|
PlaySqlQuery,
|
|
156
158
|
PlayReceiptWaitMs,
|
|
157
159
|
PlayRuntimeTimeoutMs,
|
|
@@ -253,6 +255,8 @@ export type SqlListenerEvent<T extends object = Record<string, unknown>> =
|
|
|
253
255
|
/** @deprecated Pass a SQL string directly to ctx.customerDb.query. */
|
|
254
256
|
export type SqlQuery = PlaySqlQuery;
|
|
255
257
|
export type SecretHandle = PlaySecretHandle;
|
|
258
|
+
export type SecretPromise = PlaySecretPromise;
|
|
259
|
+
export type SecretValue = PlaySecretValue;
|
|
256
260
|
export type SecretAuth = PlaySecretAuth;
|
|
257
261
|
export type SecretAuthInput = PlaySecretAuthInput;
|
|
258
262
|
export type SecretAwareRequestInit = PlaySecretAwareRequestInit;
|
|
@@ -192,7 +192,7 @@ export const SDK_RELEASE = {
|
|
|
192
192
|
// 0.3.0 introduces raw-v2: complete scrubbed provider responses are
|
|
193
193
|
// available at toolResponse.rawV2 while toolResponse.raw and all declared
|
|
194
194
|
// getters keep their established compatibility behavior.
|
|
195
|
-
version: '0.3.
|
|
195
|
+
version: '0.3.8',
|
|
196
196
|
updateSummary:
|
|
197
197
|
'New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.',
|
|
198
198
|
contracts: {
|
|
@@ -168,6 +168,9 @@ import {
|
|
|
168
168
|
validateOptionalPlayAuthoringField,
|
|
169
169
|
validatePlayAuthoringField,
|
|
170
170
|
type PlayAuthoringContractEdition,
|
|
171
|
+
type PlaySecretAuth,
|
|
172
|
+
type PlaySecretAwareRequestInit,
|
|
173
|
+
type PlaySecretPromise,
|
|
171
174
|
type PlaySqlQuery,
|
|
172
175
|
type PlayAuthoringRunScope,
|
|
173
176
|
type PlayAuthoringRuntimeContext,
|
|
@@ -260,17 +263,24 @@ import type { DocflowObservationUpsert } from './docflow-observation';
|
|
|
260
263
|
import {
|
|
261
264
|
assertNoSecretTaint,
|
|
262
265
|
assertSecretAuthUsesTls,
|
|
266
|
+
createBase64SecretValue,
|
|
263
267
|
createBearerSecretAuth,
|
|
264
268
|
createHeaderSecretAuth,
|
|
269
|
+
createPlaintextSecretPromise,
|
|
270
|
+
createSecretConcat,
|
|
265
271
|
createSecretHandle,
|
|
266
272
|
isSecretAuthInput,
|
|
273
|
+
isPlaintextSecretPromise,
|
|
274
|
+
isSecretHandle,
|
|
267
275
|
secretAuthEntries,
|
|
268
276
|
secretAuthHeaderMarkers,
|
|
269
277
|
valueContainsSecret,
|
|
270
278
|
type SecretAuth,
|
|
279
|
+
type SecretAuthValue,
|
|
271
280
|
type SecretAuthInput,
|
|
272
281
|
type SecretAwareRequestInit,
|
|
273
282
|
type SecretHandle,
|
|
283
|
+
type SecretValue,
|
|
274
284
|
} from './secret-capability';
|
|
275
285
|
import type {
|
|
276
286
|
CsvOptions,
|
|
@@ -1218,7 +1228,9 @@ function publicToolResponseForBatchedItem(
|
|
|
1218
1228
|
* from callToolAPI. Keep that input contract stable; raw-v2 is attached to the
|
|
1219
1229
|
* completed logical call separately by publicToolResponseForBatchedItem.
|
|
1220
1230
|
*/
|
|
1221
|
-
function legacyResultForBatchSplitter(
|
|
1231
|
+
function legacyResultForBatchSplitter(
|
|
1232
|
+
execution: ParsedToolExecuteResponse,
|
|
1233
|
+
): unknown {
|
|
1222
1234
|
if (execution.toolResponse && 'raw' in execution.toolResponse) {
|
|
1223
1235
|
return execution.toolResponse.raw;
|
|
1224
1236
|
}
|
|
@@ -2260,18 +2272,33 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2260
2272
|
};
|
|
2261
2273
|
|
|
2262
2274
|
readonly secrets = {
|
|
2263
|
-
get: (name: string):
|
|
2275
|
+
get: (name: string): PlaySecretPromise => {
|
|
2264
2276
|
if (typeof name !== 'string' || !name.trim()) {
|
|
2265
2277
|
throw new Error('ctx.secrets.get(name) requires a non-empty name.');
|
|
2266
2278
|
}
|
|
2267
|
-
|
|
2279
|
+
const handle = createSecretHandle(name.trim());
|
|
2280
|
+
const value =
|
|
2281
|
+
this.currentAuthoringContractEdition >= 4
|
|
2282
|
+
? createPlaintextSecretPromise(handle.name, () =>
|
|
2283
|
+
this.resolveSecretHandle(handle),
|
|
2284
|
+
)
|
|
2285
|
+
: handle;
|
|
2286
|
+
return value as unknown as PlaySecretPromise;
|
|
2268
2287
|
},
|
|
2269
|
-
bearer: (secret:
|
|
2270
|
-
return createBearerSecretAuth(secret);
|
|
2288
|
+
bearer: (secret: SecretAuthValue): PlaySecretAuth => {
|
|
2289
|
+
return createBearerSecretAuth(secret) as unknown as PlaySecretAuth;
|
|
2271
2290
|
},
|
|
2272
|
-
header: (header: string, secret:
|
|
2273
|
-
return createHeaderSecretAuth(
|
|
2291
|
+
header: (header: string, secret: SecretAuthValue): PlaySecretAuth => {
|
|
2292
|
+
return createHeaderSecretAuth(
|
|
2293
|
+
header,
|
|
2294
|
+
secret,
|
|
2295
|
+
) as unknown as PlaySecretAuth;
|
|
2274
2296
|
},
|
|
2297
|
+
// Only editions 1–3 call these runtime-only compatibility helpers. Edition
|
|
2298
|
+
// 4's public authoring contract intentionally exposes normal strings.
|
|
2299
|
+
concat: (...parts: readonly (string | SecretValue)[]): SecretValue =>
|
|
2300
|
+
createSecretConcat(parts),
|
|
2301
|
+
base64: (value: SecretValue): SecretValue => createBase64SecretValue(value),
|
|
2275
2302
|
};
|
|
2276
2303
|
|
|
2277
2304
|
constructor(options: ContextOptions) {
|
|
@@ -2780,10 +2807,52 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2780
2807
|
|
|
2781
2808
|
private async resolveSingleSecretAuth(auth: SecretAuth) {
|
|
2782
2809
|
if (!auth) return {};
|
|
2810
|
+
const value = await this.resolveSecretAuthValue(auth.secret);
|
|
2811
|
+
if (auth.kind === 'bearer') {
|
|
2812
|
+
return { authorization: `Bearer ${value}` };
|
|
2813
|
+
}
|
|
2814
|
+
return { [auth.header.toLowerCase()]: value };
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
private async resolveSecretAuthValue(
|
|
2818
|
+
secret: SecretAuthValue,
|
|
2819
|
+
): Promise<string> {
|
|
2820
|
+
if (typeof secret === 'string') {
|
|
2821
|
+
this.secretRedactor.register(secret);
|
|
2822
|
+
return secret;
|
|
2823
|
+
}
|
|
2824
|
+
if (isPlaintextSecretPromise(secret)) {
|
|
2825
|
+
const value = await secret;
|
|
2826
|
+
this.secretRedactor.register(value);
|
|
2827
|
+
return value;
|
|
2828
|
+
}
|
|
2829
|
+
return this.resolveSecretValue(secret as SecretValue);
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
private async resolveSecretValue(secret: SecretValue): Promise<string> {
|
|
2833
|
+
if (isSecretHandle(secret)) return this.resolveSecretHandle(secret);
|
|
2834
|
+
const value =
|
|
2835
|
+
secret.kind === 'concat'
|
|
2836
|
+
? (
|
|
2837
|
+
await Promise.all(
|
|
2838
|
+
secret.parts.map((part) =>
|
|
2839
|
+
typeof part === 'string' ? part : this.resolveSecretValue(part),
|
|
2840
|
+
),
|
|
2841
|
+
)
|
|
2842
|
+
).join('')
|
|
2843
|
+
: Buffer.from(
|
|
2844
|
+
await this.resolveSecretValue(secret.value),
|
|
2845
|
+
'utf8',
|
|
2846
|
+
).toString('base64');
|
|
2847
|
+
this.secretRedactor.register(value);
|
|
2848
|
+
return value;
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2851
|
+
private async resolveSecretHandle(secret: SecretHandle): Promise<string> {
|
|
2783
2852
|
let value: string | null = null;
|
|
2784
2853
|
if (this.#options.resolveSecret) {
|
|
2785
2854
|
value = await this.#options.resolveSecret({
|
|
2786
|
-
name:
|
|
2855
|
+
name: secret.name,
|
|
2787
2856
|
playName: this.#options.playName,
|
|
2788
2857
|
orgId: this.#options.orgId,
|
|
2789
2858
|
workflowId: this.#options.workflowId,
|
|
@@ -2824,7 +2893,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2824
2893
|
},
|
|
2825
2894
|
body: JSON.stringify({
|
|
2826
2895
|
action: 'resolve_secret',
|
|
2827
|
-
name:
|
|
2896
|
+
name: secret.name,
|
|
2828
2897
|
playName: this.#options.playName,
|
|
2829
2898
|
}),
|
|
2830
2899
|
});
|
|
@@ -2837,7 +2906,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2837
2906
|
this.log(
|
|
2838
2907
|
`[runtime.secret_resolution_failure] ${JSON.stringify({
|
|
2839
2908
|
stage: 'transport',
|
|
2840
|
-
secret_name:
|
|
2909
|
+
secret_name: secret.name,
|
|
2841
2910
|
gateway_origin: transportGatewayOriginForDiagnostic(url),
|
|
2842
2911
|
operation_id: operationId,
|
|
2843
2912
|
request_id: requestId,
|
|
@@ -2852,7 +2921,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2852
2921
|
);
|
|
2853
2922
|
if (!retry.retry) {
|
|
2854
2923
|
throw new Error(
|
|
2855
|
-
`Secret ${
|
|
2924
|
+
`Secret ${secret.name} resolution transport failed (request_id=${requestId}): ${diagnostic.message ?? 'unknown transport error'}`,
|
|
2856
2925
|
);
|
|
2857
2926
|
}
|
|
2858
2927
|
await waitForSecretResolutionRetry(retry.retryDelayMs);
|
|
@@ -2865,7 +2934,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2865
2934
|
: NO_SECRET_RESOLUTION_RETRY;
|
|
2866
2935
|
const responseDiagnostic = {
|
|
2867
2936
|
stage: response.ok ? 'invalid_response' : 'http_response',
|
|
2868
|
-
secret_name:
|
|
2937
|
+
secret_name: secret.name,
|
|
2869
2938
|
gateway_origin: transportGatewayOriginForDiagnostic(url),
|
|
2870
2939
|
operation_id: operationId,
|
|
2871
2940
|
request_id: requestId,
|
|
@@ -2898,7 +2967,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2898
2967
|
continue;
|
|
2899
2968
|
}
|
|
2900
2969
|
throw new Error(
|
|
2901
|
-
`Secret ${
|
|
2970
|
+
`Secret ${secret.name} is not available to this run (resolution status=${response.status}, request_id=${requestId}).`,
|
|
2902
2971
|
);
|
|
2903
2972
|
}
|
|
2904
2973
|
|
|
@@ -2915,7 +2984,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2915
2984
|
})}`,
|
|
2916
2985
|
);
|
|
2917
2986
|
throw new Error(
|
|
2918
|
-
`Secret ${
|
|
2987
|
+
`Secret ${secret.name} is not available to this run (invalid resolution response, request_id=${requestId}).`,
|
|
2919
2988
|
);
|
|
2920
2989
|
}
|
|
2921
2990
|
|
|
@@ -2939,13 +3008,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2939
3008
|
})}`,
|
|
2940
3009
|
);
|
|
2941
3010
|
throw new Error(
|
|
2942
|
-
`Secret ${
|
|
3011
|
+
`Secret ${secret.name} is not available to this run (missing resolution value, request_id=${requestId}).`,
|
|
2943
3012
|
);
|
|
2944
3013
|
}
|
|
2945
3014
|
if (attempt > 1) {
|
|
2946
3015
|
this.log(
|
|
2947
3016
|
`[runtime.secret_resolution_recovered] ${JSON.stringify({
|
|
2948
|
-
secret_name:
|
|
3017
|
+
secret_name: secret.name,
|
|
2949
3018
|
gateway_origin: transportGatewayOriginForDiagnostic(url),
|
|
2950
3019
|
operation_id: operationId,
|
|
2951
3020
|
request_id: requestId,
|
|
@@ -2962,15 +3031,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2962
3031
|
);
|
|
2963
3032
|
}
|
|
2964
3033
|
if (!value) {
|
|
2965
|
-
throw new Error(
|
|
2966
|
-
`Secret ${auth.secret.name} is not available to this run.`,
|
|
2967
|
-
);
|
|
3034
|
+
throw new Error(`Secret ${secret.name} is not available to this run.`);
|
|
2968
3035
|
}
|
|
2969
3036
|
this.secretRedactor.register(value);
|
|
2970
|
-
|
|
2971
|
-
return { authorization: `Bearer ${value}` };
|
|
2972
|
-
}
|
|
2973
|
-
return { [auth.header.toLowerCase()]: value };
|
|
3037
|
+
return value;
|
|
2974
3038
|
}
|
|
2975
3039
|
|
|
2976
3040
|
private setMapFrame(frame: MapExecutionFrame): void {
|
|
@@ -9709,7 +9773,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9709
9773
|
|
|
9710
9774
|
log(msg: string): void {
|
|
9711
9775
|
assertNoSecretTaint(msg, 'ctx.log');
|
|
9712
|
-
const line = `[${new Date().toISOString()}] ${msg}`;
|
|
9776
|
+
const line = `[${new Date().toISOString()}] ${this.secretRedactor.redactRegisteredSecrets(msg)}`;
|
|
9713
9777
|
this.logBuffer.push(line);
|
|
9714
9778
|
this.#options.onLog?.(line);
|
|
9715
9779
|
if (this.#options.verbose) console.log(line);
|
|
@@ -9750,7 +9814,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9750
9814
|
async fetch(
|
|
9751
9815
|
key: string,
|
|
9752
9816
|
input: string | URL,
|
|
9753
|
-
init: SecretAwareRequestInit = {},
|
|
9817
|
+
init: PlaySecretAwareRequestInit | SecretAwareRequestInit = {},
|
|
9754
9818
|
options?: FetchOptions,
|
|
9755
9819
|
): Promise<PlayFetchResponse> {
|
|
9756
9820
|
validatePlayAuthoringField('ctx.fetch.key', key);
|
|
@@ -9765,25 +9829,68 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9765
9829
|
}
|
|
9766
9830
|
: null;
|
|
9767
9831
|
|
|
9768
|
-
|
|
9832
|
+
const url = input.toString();
|
|
9833
|
+
const parsedUrl = new URL(url);
|
|
9834
|
+
const urlContainsResolvedSecret =
|
|
9835
|
+
this.secretRedactor.containsRegisteredSecret(url, {
|
|
9836
|
+
includeEncoded: true,
|
|
9837
|
+
minimumLength: 4,
|
|
9838
|
+
}) ||
|
|
9839
|
+
[...parsedUrl.searchParams.values()].some((value) =>
|
|
9840
|
+
this.secretRedactor.matchesRegisteredSecret(value),
|
|
9841
|
+
);
|
|
9842
|
+
if (valueContainsSecret(input) || urlContainsResolvedSecret) {
|
|
9843
|
+
throw new Error(
|
|
9844
|
+
'ctx.fetch does not allow secrets in the URL. Use an approved secret auth helper or request body.',
|
|
9845
|
+
);
|
|
9846
|
+
}
|
|
9847
|
+
if (valueContainsSecret(init.body)) {
|
|
9769
9848
|
throw new Error(
|
|
9770
|
-
'ctx.fetch does not allow
|
|
9849
|
+
'ctx.fetch does not allow opaque secret values in the body. Await ctx.secrets.get(...) before constructing the body.',
|
|
9771
9850
|
);
|
|
9772
9851
|
}
|
|
9773
|
-
|
|
9852
|
+
const requestBody = typeof init.body === 'string' ? init.body : null;
|
|
9853
|
+
const bodyContainsResolvedSecret =
|
|
9854
|
+
requestBody !== null &&
|
|
9855
|
+
this.secretRedactor.containsRegisteredSecret(requestBody, {
|
|
9856
|
+
includeEncoded: true,
|
|
9857
|
+
});
|
|
9858
|
+
if (bodyContainsResolvedSecret && parsedUrl.protocol !== 'https:') {
|
|
9859
|
+
throw new Error(
|
|
9860
|
+
'ctx.fetch with a resolved secret in the request body requires an https:// URL. Customer secrets may only leave Deepline over TLS.',
|
|
9861
|
+
);
|
|
9862
|
+
}
|
|
9863
|
+
const receiptBody = bodyContainsResolvedSecret
|
|
9864
|
+
? this.secretRedactor.redactRegisteredSecrets(requestBody)
|
|
9865
|
+
: requestBody;
|
|
9866
|
+
const rawHeaders = normalizeFetchHeaders(init.headers);
|
|
9867
|
+
if (
|
|
9868
|
+
valueContainsSecret(init.headers) ||
|
|
9869
|
+
Object.values(rawHeaders).some(
|
|
9870
|
+
(value) =>
|
|
9871
|
+
this.secretRedactor.matchesRegisteredSecret(value) ||
|
|
9872
|
+
this.secretRedactor.containsRegisteredSecret(value, {
|
|
9873
|
+
minimumLength: 4,
|
|
9874
|
+
}),
|
|
9875
|
+
)
|
|
9876
|
+
) {
|
|
9774
9877
|
throw new Error(
|
|
9775
9878
|
'ctx.fetch does not allow raw secret headers. Use ctx.secrets.bearer(...) or ctx.secrets.header(...).',
|
|
9776
9879
|
);
|
|
9777
9880
|
}
|
|
9778
|
-
|
|
9779
|
-
|
|
9881
|
+
let secretAuth: SecretAuthInput | undefined;
|
|
9882
|
+
if (init.auth !== undefined) {
|
|
9883
|
+
if (!isSecretAuthInput(init.auth)) {
|
|
9884
|
+
throw new Error('ctx.fetch auth must come from ctx.secrets.');
|
|
9885
|
+
}
|
|
9886
|
+
secretAuth = init.auth;
|
|
9780
9887
|
}
|
|
9781
9888
|
// Secret handles are deliberately resolved at the last possible moment, so
|
|
9782
9889
|
// plaintext never lands in durable keys, receipts, map rows, or generic tool
|
|
9783
9890
|
// payloads. The one place a customer secret is allowed to leave Deepline is
|
|
9784
9891
|
// the requested auth header, and that transport must be TLS.
|
|
9785
|
-
assertSecretAuthUsesTls(
|
|
9786
|
-
const secretHeaderMarkers = secretAuthHeaderMarkers(
|
|
9892
|
+
assertSecretAuthUsesTls(secretAuth, input, 'ctx.fetch');
|
|
9893
|
+
const secretHeaderMarkers = secretAuthHeaderMarkers(secretAuth);
|
|
9787
9894
|
|
|
9788
9895
|
const execution = this.executeWithRuntimeReceipt<PlayFetchResponse>(
|
|
9789
9896
|
'fetch',
|
|
@@ -9793,8 +9900,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9793
9900
|
semanticKey: stableDigest(
|
|
9794
9901
|
stableStringify({
|
|
9795
9902
|
method: (init.method ?? 'GET').toUpperCase(),
|
|
9796
|
-
url
|
|
9797
|
-
body:
|
|
9903
|
+
url,
|
|
9904
|
+
body: receiptBody,
|
|
9798
9905
|
safeHeaders: {
|
|
9799
9906
|
...normalizeFetchHeaders(init.headers),
|
|
9800
9907
|
...secretHeaderMarkers,
|
|
@@ -9804,9 +9911,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9804
9911
|
),
|
|
9805
9912
|
staleAfterSeconds: options?.staleAfterSeconds,
|
|
9806
9913
|
execute: async ({ retainExternalCallSlot }) => {
|
|
9807
|
-
const url = input.toString();
|
|
9808
9914
|
const method = (init.method ?? 'GET').toUpperCase();
|
|
9809
|
-
const secretHeaders = await this.resolveSecretAuth(
|
|
9915
|
+
const secretHeaders = await this.resolveSecretAuth(secretAuth);
|
|
9810
9916
|
const headers: Record<string, string> = {
|
|
9811
9917
|
...normalizeFetchHeaders(init.headers),
|
|
9812
9918
|
...secretHeaders,
|
|
@@ -9822,7 +9928,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9822
9928
|
...normalizeFetchHeaders(init.headers),
|
|
9823
9929
|
...secretHeaderMarkers,
|
|
9824
9930
|
},
|
|
9825
|
-
body:
|
|
9931
|
+
body: receiptBody,
|
|
9826
9932
|
row: rowFetchScope,
|
|
9827
9933
|
}),
|
|
9828
9934
|
)}`,
|
|
@@ -9909,6 +10015,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9909
10015
|
safePublicFetch(url, attemptInit, {
|
|
9910
10016
|
fetchImpl: this.#options.fetchImpl,
|
|
9911
10017
|
sensitiveHeaders: Object.keys(secretHeaderMarkers),
|
|
10018
|
+
stripHeadersOnCrossOriginRedirect: true,
|
|
9912
10019
|
}),
|
|
9913
10020
|
});
|
|
9914
10021
|
bodyText = await readCtxFetchBody({
|
package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { DAYTONA_PLAY_RUNNER_LABEL_SOURCE } from './daytona-labels';
|
|
|
15
15
|
const DAYTONA_CREATE_TIMEOUT_SECONDS = 10;
|
|
16
16
|
const DAYTONA_CREATE_RETRY_DELAYS_MS = [0, 500, 1_500] as const;
|
|
17
17
|
const DAYTONA_TIMED_OUT_CREATE_RECONCILE_DELAYS_MS = [
|
|
18
|
-
0,
|
|
18
|
+
0, 1_000, 3_000, 6_000, 12_000,
|
|
19
19
|
] as const;
|
|
20
20
|
// Explicit runner deadline + scheduler GC own the normal lifecycle. Daytona's
|
|
21
21
|
// inactivity stop is a wider crash backstop measured from sandbox creation, so
|
|
@@ -2,10 +2,16 @@ import type {
|
|
|
2
2
|
PlaySecretAuth,
|
|
3
3
|
PlaySecretAwareRequestInit,
|
|
4
4
|
PlaySecretHandle,
|
|
5
|
+
PlaySecretPromise,
|
|
5
6
|
} from '../plays/authoring-contract';
|
|
7
|
+
import { sha256Hex } from '../plays/row-identity';
|
|
6
8
|
|
|
7
9
|
const SECRET_HANDLE_BRAND = Symbol.for('deepline.secret.handle');
|
|
8
10
|
const SECRET_AUTH_BRAND = Symbol.for('deepline.secret.auth');
|
|
11
|
+
const SECRET_VALUE_BRAND = Symbol.for('deepline.secret.value');
|
|
12
|
+
const SECRET_PLAINTEXT_PROMISE_BRAND = Symbol.for(
|
|
13
|
+
'deepline.secret.plaintext-promise',
|
|
14
|
+
);
|
|
9
15
|
const SECRET_HANDLE_MARKER_RE = /\[secret:[A-Z0-9_ -]+\]/i;
|
|
10
16
|
|
|
11
17
|
export type SecretHandle = PlaySecretHandle & {
|
|
@@ -15,26 +21,61 @@ export type SecretHandle = PlaySecretHandle & {
|
|
|
15
21
|
toJSON(): never;
|
|
16
22
|
};
|
|
17
23
|
|
|
18
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Runtime-only opaque values retained to execute authoring-contract editions
|
|
26
|
+
* 1–3. Edition 4's public Play type deliberately no longer exposes these
|
|
27
|
+
* helpers; published legacy artifacts still invoke them at runtime.
|
|
28
|
+
*/
|
|
29
|
+
type SecretExpression =
|
|
30
|
+
| {
|
|
31
|
+
readonly [SECRET_VALUE_BRAND]: true;
|
|
32
|
+
readonly kind: 'concat';
|
|
33
|
+
readonly parts: readonly (string | SecretValue)[];
|
|
34
|
+
toString(): string;
|
|
35
|
+
toJSON(): never;
|
|
36
|
+
}
|
|
37
|
+
| {
|
|
38
|
+
readonly [SECRET_VALUE_BRAND]: true;
|
|
39
|
+
readonly kind: 'base64';
|
|
40
|
+
readonly value: SecretValue;
|
|
41
|
+
toString(): string;
|
|
42
|
+
toJSON(): never;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type SecretValue = SecretHandle | SecretExpression;
|
|
46
|
+
export type PlaintextSecretPromise = PlaySecretPromise & {
|
|
47
|
+
readonly [SECRET_PLAINTEXT_PROMISE_BRAND]: true;
|
|
48
|
+
readonly name: string;
|
|
49
|
+
};
|
|
50
|
+
export type SecretAuthValue =
|
|
51
|
+
| string
|
|
52
|
+
| PlaySecretPromise
|
|
53
|
+
| PlaintextSecretPromise
|
|
54
|
+
| PlaySecretHandle
|
|
55
|
+
| SecretValue;
|
|
56
|
+
|
|
57
|
+
export type SecretAuth = Omit<PlaySecretAuth, 'secret'> &
|
|
19
58
|
(
|
|
20
59
|
| {
|
|
21
60
|
readonly [SECRET_AUTH_BRAND]: true;
|
|
22
61
|
readonly kind: 'bearer';
|
|
23
|
-
readonly secret:
|
|
62
|
+
readonly secret: SecretAuthValue;
|
|
24
63
|
}
|
|
25
64
|
| {
|
|
26
65
|
readonly [SECRET_AUTH_BRAND]: true;
|
|
27
66
|
readonly kind: 'header';
|
|
28
67
|
readonly header: string;
|
|
29
|
-
readonly secret:
|
|
68
|
+
readonly secret: SecretAuthValue;
|
|
30
69
|
}
|
|
31
70
|
);
|
|
32
71
|
|
|
33
72
|
export type SecretAuthInput = SecretAuth | readonly SecretAuth[];
|
|
34
73
|
|
|
35
|
-
export type SecretAwareRequestInit =
|
|
36
|
-
|
|
37
|
-
|
|
74
|
+
export type SecretAwareRequestInit =
|
|
75
|
+
| PlaySecretAwareRequestInit
|
|
76
|
+
| (Omit<PlaySecretAwareRequestInit, 'auth'> & {
|
|
77
|
+
auth?: SecretAuthInput;
|
|
78
|
+
});
|
|
38
79
|
|
|
39
80
|
function isRecord(value: unknown): value is Record<string | symbol, unknown> {
|
|
40
81
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -44,6 +85,25 @@ export function isSecretHandle(value: unknown): value is SecretHandle {
|
|
|
44
85
|
return isRecord(value) && value[SECRET_HANDLE_BRAND] === true;
|
|
45
86
|
}
|
|
46
87
|
|
|
88
|
+
export function isSecretValue(value: unknown): value is SecretValue {
|
|
89
|
+
return (
|
|
90
|
+
isSecretHandle(value) ||
|
|
91
|
+
(isRecord(value) && value[SECRET_VALUE_BRAND] === true)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function isPlaintextSecretPromise(
|
|
96
|
+
value: unknown,
|
|
97
|
+
): value is PlaintextSecretPromise {
|
|
98
|
+
return (
|
|
99
|
+
Boolean(value) &&
|
|
100
|
+
typeof value === 'object' &&
|
|
101
|
+
(value as Record<string | symbol, unknown>)[
|
|
102
|
+
SECRET_PLAINTEXT_PROMISE_BRAND
|
|
103
|
+
] === true
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
47
107
|
export function isSecretAuth(value: unknown): value is SecretAuth {
|
|
48
108
|
return isRecord(value) && value[SECRET_AUTH_BRAND] === true;
|
|
49
109
|
}
|
|
@@ -69,7 +129,7 @@ export function valueContainsSecret(value: unknown): boolean {
|
|
|
69
129
|
const seen = new WeakSet<object>();
|
|
70
130
|
while (pending.length > 0) {
|
|
71
131
|
const candidate = pending.pop();
|
|
72
|
-
if (
|
|
132
|
+
if (isSecretValue(candidate) || isSecretAuth(candidate)) return true;
|
|
73
133
|
if (typeof candidate === 'string') {
|
|
74
134
|
if (SECRET_HANDLE_MARKER_RE.test(candidate)) return true;
|
|
75
135
|
continue;
|
|
@@ -99,19 +159,47 @@ export function createSecretHandle(name: string): SecretHandle {
|
|
|
99
159
|
} as unknown as SecretHandle;
|
|
100
160
|
}
|
|
101
161
|
|
|
102
|
-
export function
|
|
103
|
-
|
|
104
|
-
|
|
162
|
+
export function createPlaintextSecretPromise(
|
|
163
|
+
name: string,
|
|
164
|
+
resolve: () => Promise<string>,
|
|
165
|
+
): PlaintextSecretPromise {
|
|
166
|
+
const value = resolve() as PlaintextSecretPromise;
|
|
167
|
+
Object.defineProperties(value, {
|
|
168
|
+
[SECRET_PLAINTEXT_PROMISE_BRAND]: { value: true },
|
|
169
|
+
name: { value: name },
|
|
170
|
+
});
|
|
171
|
+
return value;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function createBearerSecretAuth(secret: SecretAuthValue): SecretAuth {
|
|
175
|
+
// A generic promise has no secret provenance or stable receipt marker. Only
|
|
176
|
+
// the promise created by ctx.secrets.get(...) carries the runtime brand.
|
|
177
|
+
if (
|
|
178
|
+
typeof secret !== 'string' &&
|
|
179
|
+
!isSecretValue(secret) &&
|
|
180
|
+
!isPlaintextSecretPromise(secret)
|
|
181
|
+
) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
'ctx.secrets.bearer(...) requires a resolved string or legacy secret handle. Await ctx.secrets.get(...) first.',
|
|
184
|
+
);
|
|
105
185
|
}
|
|
106
186
|
return { [SECRET_AUTH_BRAND]: true, kind: 'bearer', secret };
|
|
107
187
|
}
|
|
108
188
|
|
|
109
189
|
export function createHeaderSecretAuth(
|
|
110
190
|
header: string,
|
|
111
|
-
secret:
|
|
191
|
+
secret: SecretAuthValue,
|
|
112
192
|
): SecretAuth {
|
|
113
|
-
|
|
114
|
-
|
|
193
|
+
// Keep this check in sync with bearer: ordinary promises must be awaited
|
|
194
|
+
// before auth construction, while the branded get() promise is compatible.
|
|
195
|
+
if (
|
|
196
|
+
typeof secret !== 'string' &&
|
|
197
|
+
!isSecretValue(secret) &&
|
|
198
|
+
!isPlaintextSecretPromise(secret)
|
|
199
|
+
) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
'ctx.secrets.header(...) requires a resolved string or legacy secret handle. Await ctx.secrets.get(...) first.',
|
|
202
|
+
);
|
|
115
203
|
}
|
|
116
204
|
if (typeof header !== 'string' || !header.trim()) {
|
|
117
205
|
throw new Error('ctx.secrets.header(...) requires a header name.');
|
|
@@ -124,6 +212,62 @@ export function createHeaderSecretAuth(
|
|
|
124
212
|
};
|
|
125
213
|
}
|
|
126
214
|
|
|
215
|
+
export function createSecretConcat(
|
|
216
|
+
parts: readonly (string | SecretValue)[],
|
|
217
|
+
): SecretValue {
|
|
218
|
+
if (parts.length === 0 || !parts.some(isSecretValue)) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
'ctx.secrets.concat(...) requires at least one secret value.',
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
if (!parts.every((part) => typeof part === 'string' || isSecretValue(part))) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
'ctx.secrets.concat(...) accepts strings and secret values only.',
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
[SECRET_VALUE_BRAND]: true,
|
|
230
|
+
kind: 'concat',
|
|
231
|
+
parts,
|
|
232
|
+
toString: () => '[secret-expression]',
|
|
233
|
+
toJSON: () => {
|
|
234
|
+
throw new Error(
|
|
235
|
+
'Secret expressions cannot be serialized. Use them only with ctx.secrets auth helpers.',
|
|
236
|
+
);
|
|
237
|
+
},
|
|
238
|
+
} as SecretValue;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function createBase64SecretValue(value: SecretValue): SecretValue {
|
|
242
|
+
if (!isSecretValue(value)) {
|
|
243
|
+
throw new Error('ctx.secrets.base64(...) requires a secret value.');
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
[SECRET_VALUE_BRAND]: true,
|
|
247
|
+
kind: 'base64',
|
|
248
|
+
value,
|
|
249
|
+
toString: () => '[secret-expression]',
|
|
250
|
+
toJSON: () => {
|
|
251
|
+
throw new Error(
|
|
252
|
+
'Secret expressions cannot be serialized. Use them only with ctx.secrets auth helpers.',
|
|
253
|
+
);
|
|
254
|
+
},
|
|
255
|
+
} as SecretValue;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function secretValueMarker(value: SecretValue): string {
|
|
259
|
+
if (isSecretHandle(value)) return `[secret:${value.name}]`;
|
|
260
|
+
if (value.kind === 'base64')
|
|
261
|
+
return `[secret-base64:${secretValueMarker(value.value)}]`;
|
|
262
|
+
return `[secret-concat:${value.parts
|
|
263
|
+
.map((part) =>
|
|
264
|
+
typeof part === 'string'
|
|
265
|
+
? `[literal:${sha256Hex(part)}]`
|
|
266
|
+
: secretValueMarker(part),
|
|
267
|
+
)
|
|
268
|
+
.join('+')}]`;
|
|
269
|
+
}
|
|
270
|
+
|
|
127
271
|
export function secretAuthHeaderMarkers(
|
|
128
272
|
auth: SecretAuthInput | undefined,
|
|
129
273
|
): Record<string, string> {
|
|
@@ -136,7 +280,12 @@ export function secretAuthHeaderMarkers(
|
|
|
136
280
|
`ctx.fetch cannot attach more than one secret to the ${header} header.`,
|
|
137
281
|
);
|
|
138
282
|
}
|
|
139
|
-
markers[header] =
|
|
283
|
+
markers[header] =
|
|
284
|
+
typeof entry.secret === 'string'
|
|
285
|
+
? `[secret-plaintext:${sha256Hex(entry.secret)}]`
|
|
286
|
+
: isPlaintextSecretPromise(entry.secret)
|
|
287
|
+
? `[secret:${entry.secret.name}]`
|
|
288
|
+
: secretValueMarker(entry.secret as SecretValue);
|
|
140
289
|
}
|
|
141
290
|
return markers;
|
|
142
291
|
}
|