deepline 0.3.7 → 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 +43 -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
|
@@ -5,6 +5,139 @@ const PRIVATE_KEY_PATTERN =
|
|
|
5
5
|
const BEARER_LITERAL_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/i;
|
|
6
6
|
const ASSIGNMENT_SECRET_LITERAL_PATTERN =
|
|
7
7
|
/\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*['"][^'"]{12,}['"]/i;
|
|
8
|
+
const SECRET_AUTH_LITERAL_FINDING =
|
|
9
|
+
'literal credential passed to ctx.secrets auth helper';
|
|
10
|
+
|
|
11
|
+
function skipQuoted(source: string, start: number): number {
|
|
12
|
+
const quote = source[start];
|
|
13
|
+
let index = start + 1;
|
|
14
|
+
while (index < source.length) {
|
|
15
|
+
if (source[index] === '\\') {
|
|
16
|
+
index += 2;
|
|
17
|
+
} else if (source[index] === quote) {
|
|
18
|
+
return index + 1;
|
|
19
|
+
} else {
|
|
20
|
+
index += 1;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return source.length;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function skipTrivia(source: string, start: number): number {
|
|
27
|
+
let index = start;
|
|
28
|
+
while (index < source.length) {
|
|
29
|
+
if (/\s/.test(source[index])) {
|
|
30
|
+
index += 1;
|
|
31
|
+
} else if (source.startsWith('//', index)) {
|
|
32
|
+
index = source.indexOf('\n', index + 2);
|
|
33
|
+
if (index === -1) return source.length;
|
|
34
|
+
} else if (source.startsWith('/*', index)) {
|
|
35
|
+
index = source.indexOf('*/', index + 2);
|
|
36
|
+
if (index === -1) return source.length;
|
|
37
|
+
index += 2;
|
|
38
|
+
} else {
|
|
39
|
+
return index;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return index;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function consumeIdentifier(
|
|
46
|
+
source: string,
|
|
47
|
+
start: number,
|
|
48
|
+
expected: string,
|
|
49
|
+
): number | undefined {
|
|
50
|
+
return source.startsWith(expected, start) &&
|
|
51
|
+
!/[A-Za-z0-9_$]/.test(source[start - 1] ?? '') &&
|
|
52
|
+
!/[A-Za-z0-9_$]/.test(source[start + expected.length] ?? '')
|
|
53
|
+
? start + expected.length
|
|
54
|
+
: undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function consumeMember(
|
|
58
|
+
source: string,
|
|
59
|
+
start: number,
|
|
60
|
+
expected: string,
|
|
61
|
+
): number | undefined {
|
|
62
|
+
let index = skipTrivia(source, start);
|
|
63
|
+
if (source[index] === '.') {
|
|
64
|
+
return consumeIdentifier(source, skipTrivia(source, index + 1), expected);
|
|
65
|
+
}
|
|
66
|
+
if (source[index] !== '[') return undefined;
|
|
67
|
+
index = skipTrivia(source, index + 1);
|
|
68
|
+
if (source[index] !== "'" && source[index] !== '"') return undefined;
|
|
69
|
+
const end = skipQuoted(source, index);
|
|
70
|
+
if (source.slice(index + 1, end - 1) !== expected) return undefined;
|
|
71
|
+
index = skipTrivia(source, end);
|
|
72
|
+
return source[index] === ']' ? index + 1 : undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function skipExpression(source: string, start: number): number {
|
|
76
|
+
let index = start;
|
|
77
|
+
let depth = 0;
|
|
78
|
+
while (index < source.length) {
|
|
79
|
+
const char = source[index];
|
|
80
|
+
if (char === "'" || char === '"' || char === '`') {
|
|
81
|
+
index = skipQuoted(source, index);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (source.startsWith('//', index) || source.startsWith('/*', index)) {
|
|
85
|
+
index = skipTrivia(source, index);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (char === '(' || char === '[' || char === '{') {
|
|
89
|
+
depth += 1;
|
|
90
|
+
} else if (char === ')' || char === ']' || char === '}') {
|
|
91
|
+
if (depth === 0) return index;
|
|
92
|
+
depth -= 1;
|
|
93
|
+
} else if (char === ',' && depth === 0) {
|
|
94
|
+
return index;
|
|
95
|
+
}
|
|
96
|
+
index += 1;
|
|
97
|
+
}
|
|
98
|
+
return index;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isDirectStringLiteral(source: string, start: number): boolean {
|
|
102
|
+
const quote = source[start];
|
|
103
|
+
if (quote !== "'" && quote !== '"' && quote !== '`') return false;
|
|
104
|
+
const end = skipQuoted(source, start);
|
|
105
|
+
return quote !== '`' || !source.slice(start, end).includes('${');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function hasLiteralSecretAuthCredential(source: string): boolean {
|
|
109
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
110
|
+
const char = source[index];
|
|
111
|
+
if (char === "'" || char === '"' || char === '`') {
|
|
112
|
+
index = skipQuoted(source, index) - 1;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (source.startsWith('//', index) || source.startsWith('/*', index)) {
|
|
116
|
+
index = skipTrivia(source, index) - 1;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const afterCtx = consumeIdentifier(source, index, 'ctx');
|
|
121
|
+
if (!afterCtx) continue;
|
|
122
|
+
const afterSecrets = consumeMember(source, afterCtx, 'secrets');
|
|
123
|
+
if (!afterSecrets) continue;
|
|
124
|
+
const afterHeader = consumeMember(source, afterSecrets, 'header');
|
|
125
|
+
const helper = afterHeader ? 'header' : 'bearer';
|
|
126
|
+
const afterHelper =
|
|
127
|
+
afterHeader ?? consumeMember(source, afterSecrets, 'bearer');
|
|
128
|
+
if (!afterHelper) continue;
|
|
129
|
+
const afterOpen = skipTrivia(source, afterHelper);
|
|
130
|
+
if (source[afterOpen] !== '(') continue;
|
|
131
|
+
|
|
132
|
+
const firstArgument = skipTrivia(source, afterOpen + 1);
|
|
133
|
+
const credential =
|
|
134
|
+
helper === 'header'
|
|
135
|
+
? skipTrivia(source, skipExpression(source, firstArgument) + 1)
|
|
136
|
+
: firstArgument;
|
|
137
|
+
if (isDirectStringLiteral(source, credential)) return true;
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
8
141
|
/**
|
|
9
142
|
* Returns the inline-secret findings in a string (empty if none). The throwing
|
|
10
143
|
* validator below and the workflows→plays migration validator both call this so
|
|
@@ -22,6 +155,9 @@ export function collectInlineSecretFindings(sourceCode: string): string[] {
|
|
|
22
155
|
if (ASSIGNMENT_SECRET_LITERAL_PATTERN.test(sourceCode)) {
|
|
23
156
|
findings.push('secret-looking assignment literal');
|
|
24
157
|
}
|
|
158
|
+
if (hasLiteralSecretAuthCredential(sourceCode)) {
|
|
159
|
+
findings.push(SECRET_AUTH_LITERAL_FINDING);
|
|
160
|
+
}
|
|
25
161
|
return [...new Set(findings)];
|
|
26
162
|
}
|
|
27
163
|
|
|
@@ -36,7 +172,7 @@ export function validatePlaySourceHasNoInlineSecrets(input: {
|
|
|
36
172
|
`Play source ${input.filePath} appears to contain inline secret material: ${[
|
|
37
173
|
...new Set(findings),
|
|
38
174
|
].join(', ')}.`,
|
|
39
|
-
'Author secrets in the dashboard
|
|
175
|
+
'Author secrets in the dashboard, declare them in the Play\'s top-level secrets option, and read them at runtime with await ctx.secrets.get("NAME").',
|
|
40
176
|
].join(' '),
|
|
41
177
|
);
|
|
42
178
|
}
|
|
@@ -13,6 +13,8 @@ export type SafeFetchOptions = {
|
|
|
13
13
|
fetchImpl?: SafeFetchImplementation;
|
|
14
14
|
maxRedirects?: number;
|
|
15
15
|
sensitiveHeaders?: Iterable<string>;
|
|
16
|
+
/** Drop every caller-supplied header when a redirect changes origin. */
|
|
17
|
+
stripHeadersOnCrossOriginRedirect?: boolean;
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
function removeHeader(headers: Headers, name: string) {
|
|
@@ -29,10 +31,11 @@ function requestInitForRedirect(
|
|
|
29
31
|
to: URL,
|
|
30
32
|
status: number,
|
|
31
33
|
sensitiveHeaders: Iterable<string>,
|
|
34
|
+
stripHeadersOnCrossOriginRedirect: boolean,
|
|
32
35
|
): RequestInit {
|
|
33
|
-
|
|
36
|
+
let headers = new Headers(init.headers);
|
|
34
37
|
let method = String(init.method ?? 'GET').toUpperCase();
|
|
35
|
-
let body = init.body;
|
|
38
|
+
let body = init.body ?? undefined;
|
|
36
39
|
|
|
37
40
|
if (
|
|
38
41
|
status === 303 ||
|
|
@@ -45,11 +48,23 @@ function requestInitForRedirect(
|
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
if (from.origin !== to.origin) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
if (body !== undefined) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
'Cross-origin redirect blocked because it would replay a request body.',
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (stripHeadersOnCrossOriginRedirect) {
|
|
57
|
+
// Callers that can construct credentials from plaintext secrets cannot
|
|
58
|
+
// reliably identify every transformed value. Do not forward any caller
|
|
59
|
+
// header across an origin boundary in that trust model.
|
|
60
|
+
headers = new Headers();
|
|
61
|
+
} else {
|
|
62
|
+
removeHeader(headers, 'authorization');
|
|
63
|
+
removeHeader(headers, 'cookie');
|
|
64
|
+
removeHeader(headers, 'proxy-authorization');
|
|
65
|
+
for (const header of sensitiveHeaders) {
|
|
66
|
+
removeHeader(headers, header);
|
|
67
|
+
}
|
|
53
68
|
}
|
|
54
69
|
}
|
|
55
70
|
|
|
@@ -71,6 +86,8 @@ export async function safePublicFetch(
|
|
|
71
86
|
const maxRedirects = options.maxRedirects ?? 10;
|
|
72
87
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
73
88
|
const sensitiveHeaders = options.sensitiveHeaders ?? [];
|
|
89
|
+
const stripHeadersOnCrossOriginRedirect =
|
|
90
|
+
options.stripHeadersOnCrossOriginRedirect === true;
|
|
74
91
|
let currentUrl = assertPublicHttpUrl(input);
|
|
75
92
|
let currentInit: RequestInit = {
|
|
76
93
|
...init,
|
|
@@ -117,6 +134,7 @@ export async function safePublicFetch(
|
|
|
117
134
|
nextUrl,
|
|
118
135
|
response.status,
|
|
119
136
|
sensitiveHeaders,
|
|
137
|
+
stripHeadersOnCrossOriginRedirect,
|
|
120
138
|
);
|
|
121
139
|
currentUrl = nextUrl;
|
|
122
140
|
}
|
package/dist/cli/index.js
CHANGED
|
@@ -1047,7 +1047,7 @@ var SDK_RELEASE = {
|
|
|
1047
1047
|
// 0.3.0 introduces raw-v2: complete scrubbed provider responses are
|
|
1048
1048
|
// available at toolResponse.rawV2 while toolResponse.raw and all declared
|
|
1049
1049
|
// getters keep their established compatibility behavior.
|
|
1050
|
-
version: "0.3.
|
|
1050
|
+
version: "0.3.8",
|
|
1051
1051
|
updateSummary: "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.",
|
|
1052
1052
|
contracts: {
|
|
1053
1053
|
api: {
|
|
@@ -2020,10 +2020,30 @@ function redactSecretLikeString(value) {
|
|
|
2020
2020
|
function createSecretRedactionContext(initialValues = []) {
|
|
2021
2021
|
const exactSecrets = /* @__PURE__ */ new Set();
|
|
2022
2022
|
function register(value) {
|
|
2023
|
-
if (value.length
|
|
2023
|
+
if (value.length > 0) exactSecrets.add(value);
|
|
2024
2024
|
}
|
|
2025
2025
|
for (const value of initialValues) register(value);
|
|
2026
|
-
function
|
|
2026
|
+
function containsRegisteredSecret(value, {
|
|
2027
|
+
includeEncoded = false,
|
|
2028
|
+
minimumLength = 1
|
|
2029
|
+
} = {}) {
|
|
2030
|
+
for (const secret of exactSecrets) {
|
|
2031
|
+
if (secret.length < minimumLength) continue;
|
|
2032
|
+
if (value.includes(secret)) return true;
|
|
2033
|
+
if (includeEncoded) {
|
|
2034
|
+
try {
|
|
2035
|
+
const encoded = encodeURIComponent(secret);
|
|
2036
|
+
if (encoded !== secret && value.includes(encoded)) return true;
|
|
2037
|
+
} catch {
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
return false;
|
|
2042
|
+
}
|
|
2043
|
+
function matchesRegisteredSecret(value) {
|
|
2044
|
+
return exactSecrets.has(value);
|
|
2045
|
+
}
|
|
2046
|
+
function redactRegisteredSecrets(value) {
|
|
2027
2047
|
let output2 = value;
|
|
2028
2048
|
for (const secret of exactSecrets) {
|
|
2029
2049
|
output2 = output2.replace(
|
|
@@ -2041,6 +2061,10 @@ function createSecretRedactionContext(initialValues = []) {
|
|
|
2041
2061
|
} catch {
|
|
2042
2062
|
}
|
|
2043
2063
|
}
|
|
2064
|
+
return output2;
|
|
2065
|
+
}
|
|
2066
|
+
function redactString2(value) {
|
|
2067
|
+
const output2 = redactRegisteredSecrets(value);
|
|
2044
2068
|
return redactSecretLikeString(output2);
|
|
2045
2069
|
}
|
|
2046
2070
|
function redact(value) {
|
|
@@ -2073,6 +2097,9 @@ function createSecretRedactionContext(initialValues = []) {
|
|
|
2073
2097
|
}
|
|
2074
2098
|
return {
|
|
2075
2099
|
register,
|
|
2100
|
+
containsRegisteredSecret,
|
|
2101
|
+
matchesRegisteredSecret,
|
|
2102
|
+
redactRegisteredSecrets,
|
|
2076
2103
|
redactString: redactString2,
|
|
2077
2104
|
redactKnownSecrets,
|
|
2078
2105
|
redact
|
|
@@ -16692,8 +16719,11 @@ var PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
|
|
|
16692
16719
|
` secrets?: readonly ${cloudReferenceType("bindings.secrets[]")}[];`,
|
|
16693
16720
|
"};",
|
|
16694
16721
|
"declare const SECRET_HANDLE_BRAND: unique symbol;",
|
|
16722
|
+
"declare const SECRET_PROMISE_BRAND: unique symbol;",
|
|
16695
16723
|
"export type SecretHandle = { readonly [SECRET_HANDLE_BRAND]: never; readonly name: string; toString(): string; toJSON(): never };",
|
|
16696
|
-
"export type
|
|
16724
|
+
"export type SecretPromise = Promise<string> & { readonly [SECRET_PROMISE_BRAND]: never };",
|
|
16725
|
+
"export type SecretValue = SecretHandle;",
|
|
16726
|
+
"export type SecretAuth = { readonly kind: 'bearer' | 'header'; readonly secret: string | SecretPromise | SecretHandle; readonly header?: string };",
|
|
16697
16727
|
"export type SecretAuthInput = SecretAuth | readonly SecretAuth[];",
|
|
16698
16728
|
"export type PlayInputContract<TInput> = { readonly schema: Record<string, unknown>; readonly __inputType?: TInput };",
|
|
16699
16729
|
"export type PlayReturnObject = Record<string, unknown> & { readonly _metadata?: never };",
|
|
@@ -16744,7 +16774,7 @@ var PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
|
|
|
16744
16774
|
` tool<K extends string>(key: ${cloudReferenceType("ctx.tool.key")}, toolId: K, input: ${cloudReferenceType("ctx.tool.input")}, options?: { description?: ${cloudReferenceType("ctx.tool.options.description")} }): Promise<ToolExecutionOutput<K>>;`,
|
|
16745
16775
|
" step<T>(id: string, run: () => T | Promise<T>, options?: RuntimeStepOptions): Promise<T>;",
|
|
16746
16776
|
" fetch(key: string, url: string | URL, init?: RequestInit & { headers?: HeadersInit & { 'Idempotency-Key'?: string; 'X-Idempotency-Key'?: string }; auth?: SecretAuthInput }, options?: FetchOptions): Promise<PlayFetchResponse>;",
|
|
16747
|
-
" secrets: { get(name: string):
|
|
16777
|
+
" secrets: { get(name: string): SecretPromise; bearer(secret: string | SecretPromise | SecretHandle): SecretAuth; header(header: string, secret: string | SecretPromise | SecretHandle): SecretAuth };",
|
|
16748
16778
|
` runPlay<TOutput = unknown>(key: string, playRef: ${cloudReferenceType("ctx.runPlay.playRef")}, input: ${cloudReferenceType("ctx.runPlay.input")}, options: PlayCallOptions): Promise<TOutput>;`,
|
|
16749
16779
|
" log(message: string): void;",
|
|
16750
16780
|
` sleep(ms: ${cloudReferenceType("ctx.sleep.ms")}): Promise<void>;`,
|
|
@@ -16885,15 +16915,118 @@ function listPlayFileExports(sourceCode) {
|
|
|
16885
16915
|
return exports2;
|
|
16886
16916
|
}
|
|
16887
16917
|
|
|
16888
|
-
// ../shared_libs/plays/docflow.ts
|
|
16889
|
-
var MERMAID_NODE_ATTRIBUTES = ["label", "type", "in", "out", "arm"];
|
|
16890
|
-
var LEGACY_DOCFLOW_ATTRIBUTES = ["id", ...MERMAID_NODE_ATTRIBUTES];
|
|
16891
|
-
|
|
16892
16918
|
// ../shared_libs/plays/secret-guardrails.ts
|
|
16893
16919
|
var SECRET_ENV_PATTERN = /\bprocess(?:\.env|\[['"]env['"]\])(?:\.|\[['"])([A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PRIVATE[_-]?KEY|ACCESS[_-]?KEY)[A-Z0-9_]*)(?:['"]\])?/g;
|
|
16894
16920
|
var PRIVATE_KEY_PATTERN = /-----BEGIN (?:RSA |EC |OPENSSH |PGP )?PRIVATE KEY-----/;
|
|
16895
16921
|
var BEARER_LITERAL_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/i;
|
|
16896
16922
|
var ASSIGNMENT_SECRET_LITERAL_PATTERN = /\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*['"][^'"]{12,}['"]/i;
|
|
16923
|
+
var SECRET_AUTH_LITERAL_FINDING = "literal credential passed to ctx.secrets auth helper";
|
|
16924
|
+
function skipQuoted(source, start) {
|
|
16925
|
+
const quote = source[start];
|
|
16926
|
+
let index = start + 1;
|
|
16927
|
+
while (index < source.length) {
|
|
16928
|
+
if (source[index] === "\\") {
|
|
16929
|
+
index += 2;
|
|
16930
|
+
} else if (source[index] === quote) {
|
|
16931
|
+
return index + 1;
|
|
16932
|
+
} else {
|
|
16933
|
+
index += 1;
|
|
16934
|
+
}
|
|
16935
|
+
}
|
|
16936
|
+
return source.length;
|
|
16937
|
+
}
|
|
16938
|
+
function skipTrivia(source, start) {
|
|
16939
|
+
let index = start;
|
|
16940
|
+
while (index < source.length) {
|
|
16941
|
+
if (/\s/.test(source[index])) {
|
|
16942
|
+
index += 1;
|
|
16943
|
+
} else if (source.startsWith("//", index)) {
|
|
16944
|
+
index = source.indexOf("\n", index + 2);
|
|
16945
|
+
if (index === -1) return source.length;
|
|
16946
|
+
} else if (source.startsWith("/*", index)) {
|
|
16947
|
+
index = source.indexOf("*/", index + 2);
|
|
16948
|
+
if (index === -1) return source.length;
|
|
16949
|
+
index += 2;
|
|
16950
|
+
} else {
|
|
16951
|
+
return index;
|
|
16952
|
+
}
|
|
16953
|
+
}
|
|
16954
|
+
return index;
|
|
16955
|
+
}
|
|
16956
|
+
function consumeIdentifier(source, start, expected) {
|
|
16957
|
+
return source.startsWith(expected, start) && !/[A-Za-z0-9_$]/.test(source[start - 1] ?? "") && !/[A-Za-z0-9_$]/.test(source[start + expected.length] ?? "") ? start + expected.length : void 0;
|
|
16958
|
+
}
|
|
16959
|
+
function consumeMember(source, start, expected) {
|
|
16960
|
+
let index = skipTrivia(source, start);
|
|
16961
|
+
if (source[index] === ".") {
|
|
16962
|
+
return consumeIdentifier(source, skipTrivia(source, index + 1), expected);
|
|
16963
|
+
}
|
|
16964
|
+
if (source[index] !== "[") return void 0;
|
|
16965
|
+
index = skipTrivia(source, index + 1);
|
|
16966
|
+
if (source[index] !== "'" && source[index] !== '"') return void 0;
|
|
16967
|
+
const end = skipQuoted(source, index);
|
|
16968
|
+
if (source.slice(index + 1, end - 1) !== expected) return void 0;
|
|
16969
|
+
index = skipTrivia(source, end);
|
|
16970
|
+
return source[index] === "]" ? index + 1 : void 0;
|
|
16971
|
+
}
|
|
16972
|
+
function skipExpression(source, start) {
|
|
16973
|
+
let index = start;
|
|
16974
|
+
let depth = 0;
|
|
16975
|
+
while (index < source.length) {
|
|
16976
|
+
const char = source[index];
|
|
16977
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
16978
|
+
index = skipQuoted(source, index);
|
|
16979
|
+
continue;
|
|
16980
|
+
}
|
|
16981
|
+
if (source.startsWith("//", index) || source.startsWith("/*", index)) {
|
|
16982
|
+
index = skipTrivia(source, index);
|
|
16983
|
+
continue;
|
|
16984
|
+
}
|
|
16985
|
+
if (char === "(" || char === "[" || char === "{") {
|
|
16986
|
+
depth += 1;
|
|
16987
|
+
} else if (char === ")" || char === "]" || char === "}") {
|
|
16988
|
+
if (depth === 0) return index;
|
|
16989
|
+
depth -= 1;
|
|
16990
|
+
} else if (char === "," && depth === 0) {
|
|
16991
|
+
return index;
|
|
16992
|
+
}
|
|
16993
|
+
index += 1;
|
|
16994
|
+
}
|
|
16995
|
+
return index;
|
|
16996
|
+
}
|
|
16997
|
+
function isDirectStringLiteral(source, start) {
|
|
16998
|
+
const quote = source[start];
|
|
16999
|
+
if (quote !== "'" && quote !== '"' && quote !== "`") return false;
|
|
17000
|
+
const end = skipQuoted(source, start);
|
|
17001
|
+
return quote !== "`" || !source.slice(start, end).includes("${");
|
|
17002
|
+
}
|
|
17003
|
+
function hasLiteralSecretAuthCredential(source) {
|
|
17004
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
17005
|
+
const char = source[index];
|
|
17006
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
17007
|
+
index = skipQuoted(source, index) - 1;
|
|
17008
|
+
continue;
|
|
17009
|
+
}
|
|
17010
|
+
if (source.startsWith("//", index) || source.startsWith("/*", index)) {
|
|
17011
|
+
index = skipTrivia(source, index) - 1;
|
|
17012
|
+
continue;
|
|
17013
|
+
}
|
|
17014
|
+
const afterCtx = consumeIdentifier(source, index, "ctx");
|
|
17015
|
+
if (!afterCtx) continue;
|
|
17016
|
+
const afterSecrets = consumeMember(source, afterCtx, "secrets");
|
|
17017
|
+
if (!afterSecrets) continue;
|
|
17018
|
+
const afterHeader = consumeMember(source, afterSecrets, "header");
|
|
17019
|
+
const helper = afterHeader ? "header" : "bearer";
|
|
17020
|
+
const afterHelper = afterHeader ?? consumeMember(source, afterSecrets, "bearer");
|
|
17021
|
+
if (!afterHelper) continue;
|
|
17022
|
+
const afterOpen = skipTrivia(source, afterHelper);
|
|
17023
|
+
if (source[afterOpen] !== "(") continue;
|
|
17024
|
+
const firstArgument = skipTrivia(source, afterOpen + 1);
|
|
17025
|
+
const credential = helper === "header" ? skipTrivia(source, skipExpression(source, firstArgument) + 1) : firstArgument;
|
|
17026
|
+
if (isDirectStringLiteral(source, credential)) return true;
|
|
17027
|
+
}
|
|
17028
|
+
return false;
|
|
17029
|
+
}
|
|
16897
17030
|
function collectInlineSecretFindings(sourceCode) {
|
|
16898
17031
|
const findings = [];
|
|
16899
17032
|
for (const match of sourceCode.matchAll(SECRET_ENV_PATTERN)) {
|
|
@@ -16905,6 +17038,9 @@ function collectInlineSecretFindings(sourceCode) {
|
|
|
16905
17038
|
if (ASSIGNMENT_SECRET_LITERAL_PATTERN.test(sourceCode)) {
|
|
16906
17039
|
findings.push("secret-looking assignment literal");
|
|
16907
17040
|
}
|
|
17041
|
+
if (hasLiteralSecretAuthCredential(sourceCode)) {
|
|
17042
|
+
findings.push(SECRET_AUTH_LITERAL_FINDING);
|
|
17043
|
+
}
|
|
16908
17044
|
return [...new Set(findings)];
|
|
16909
17045
|
}
|
|
16910
17046
|
|
|
@@ -25254,6 +25390,14 @@ Concepts:
|
|
|
25254
25390
|
bundle local .play.ts files for development workflows.
|
|
25255
25391
|
Running a local file does not make it live; use publish or set-live for that.
|
|
25256
25392
|
|
|
25393
|
+
Authoring:
|
|
25394
|
+
Use normal TypeScript interpolation for human-facing strings:
|
|
25395
|
+
const readyMessage = \`Put \${input.title} through to send-ready.\`;
|
|
25396
|
+
An authored diagram is optional. Docflow parses only an explicit
|
|
25397
|
+
/** @mermaid ... */ block and its // @mermaid-node annotations; ordinary
|
|
25398
|
+
prose comments are never Docflow syntax. When Docflow is enabled, plays check
|
|
25399
|
+
gives missing diagrams a non-blocking visualization warning.
|
|
25400
|
+
|
|
25257
25401
|
Common commands:
|
|
25258
25402
|
deepline plays search email --json
|
|
25259
25403
|
deepline plays describe prebuilt/person-linkedin-to-email --json
|