deepline 0.3.7 → 0.3.9
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/client.ts +1 -0
- 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 +3 -2
- package/dist/index.d.ts +3 -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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { T as ToolExecutionErrorSchemaVersion, P as PlayAuthoringContractEdition, a as PlayArtifactKind$1, b as PlaySandboxRuntimeDeclaration, c as PlayCompilerManifest } from '../compiler-manifest-
|
|
2
|
-
export { d as PLAY_ARTIFACT_KINDS } from '../compiler-manifest-
|
|
1
|
+
import { T as ToolExecutionErrorSchemaVersion, P as PlayAuthoringContractEdition, a as PlayArtifactKind$1, b as PlaySandboxRuntimeDeclaration, c as PlayCompilerManifest } from '../compiler-manifest-YIjoJo8y.js';
|
|
2
|
+
export { d as PLAY_ARTIFACT_KINDS } from '../compiler-manifest-YIjoJo8y.js';
|
|
3
3
|
import '@sinclair/typebox';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -2670,7 +2670,7 @@ __export(type_exports2, {
|
|
|
2670
2670
|
var Type = type_exports2;
|
|
2671
2671
|
|
|
2672
2672
|
// ../shared_libs/plays/authoring-contract.ts
|
|
2673
|
-
var PLAY_AUTHORING_CONTRACT_EDITION =
|
|
2673
|
+
var PLAY_AUTHORING_CONTRACT_EDITION = 4;
|
|
2674
2674
|
var PLAY_SQL_LISTENER_WHERE_OPERATORS = [
|
|
2675
2675
|
"eq",
|
|
2676
2676
|
"neq",
|
|
@@ -3941,8 +3941,11 @@ var PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
|
|
|
3941
3941
|
` secrets?: readonly ${cloudReferenceType("bindings.secrets[]")}[];`,
|
|
3942
3942
|
"};",
|
|
3943
3943
|
"declare const SECRET_HANDLE_BRAND: unique symbol;",
|
|
3944
|
+
"declare const SECRET_PROMISE_BRAND: unique symbol;",
|
|
3944
3945
|
"export type SecretHandle = { readonly [SECRET_HANDLE_BRAND]: never; readonly name: string; toString(): string; toJSON(): never };",
|
|
3945
|
-
"export type
|
|
3946
|
+
"export type SecretPromise = Promise<string> & { readonly [SECRET_PROMISE_BRAND]: never };",
|
|
3947
|
+
"export type SecretValue = SecretHandle;",
|
|
3948
|
+
"export type SecretAuth = { readonly kind: 'bearer' | 'header'; readonly secret: string | SecretPromise | SecretHandle; readonly header?: string };",
|
|
3946
3949
|
"export type SecretAuthInput = SecretAuth | readonly SecretAuth[];",
|
|
3947
3950
|
"export type PlayInputContract<TInput> = { readonly schema: Record<string, unknown>; readonly __inputType?: TInput };",
|
|
3948
3951
|
"export type PlayReturnObject = Record<string, unknown> & { readonly _metadata?: never };",
|
|
@@ -3993,7 +3996,7 @@ var PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
|
|
|
3993
3996
|
` 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>>;`,
|
|
3994
3997
|
" step<T>(id: string, run: () => T | Promise<T>, options?: RuntimeStepOptions): Promise<T>;",
|
|
3995
3998
|
" fetch(key: string, url: string | URL, init?: RequestInit & { headers?: HeadersInit & { 'Idempotency-Key'?: string; 'X-Idempotency-Key'?: string }; auth?: SecretAuthInput }, options?: FetchOptions): Promise<PlayFetchResponse>;",
|
|
3996
|
-
" secrets: { get(name: string):
|
|
3999
|
+
" secrets: { get(name: string): SecretPromise; bearer(secret: string | SecretPromise | SecretHandle): SecretAuth; header(header: string, secret: string | SecretPromise | SecretHandle): SecretAuth };",
|
|
3997
4000
|
` runPlay<TOutput = unknown>(key: string, playRef: ${cloudReferenceType("ctx.runPlay.playRef")}, input: ${cloudReferenceType("ctx.runPlay.input")}, options: PlayCallOptions): Promise<TOutput>;`,
|
|
3998
4001
|
" log(message: string): void;",
|
|
3999
4002
|
` sleep(ms: ${cloudReferenceType("ctx.sleep.ms")}): Promise<void>;`,
|
|
@@ -4192,11 +4195,9 @@ var PLAY_DOCFLOW_ARMS = [
|
|
|
4192
4195
|
"else"
|
|
4193
4196
|
];
|
|
4194
4197
|
var FLOW_START = /^\s*(?:flowchart|graph)\s+(LR|RL|TB|TD|BT)\s*$/i;
|
|
4195
|
-
var PUT = /^\s*\/\/\s*put\s+(.+)$/;
|
|
4196
4198
|
var MERMAID_NODE = /^\s*\/\/\s*@mermaid-node\s+([A-Za-z][\w-]*)(?:\s+(.*))?$/;
|
|
4197
4199
|
var ATTRIBUTE = /([A-Za-z][\w-]*)\s*:\s*"([^"\n]*)"/y;
|
|
4198
4200
|
var MERMAID_NODE_ATTRIBUTES = ["label", "type", "in", "out", "arm"];
|
|
4199
|
-
var LEGACY_DOCFLOW_ATTRIBUTES = ["id", ...MERMAID_NODE_ATTRIBUTES];
|
|
4200
4201
|
var MERMAID_NODE_ID = /^[A-Za-z][\w-]*/;
|
|
4201
4202
|
var SKETCH_CLASS = /^\s*class\s+([A-Za-z][\w-]*(?:\s*,\s*[A-Za-z][\w-]*)*)\s+sketch\s*;?\s*$/;
|
|
4202
4203
|
var MERMAID_NODE_SHAPES = [
|
|
@@ -4216,7 +4217,6 @@ var MERMAID_NODE_SHAPES = [
|
|
|
4216
4217
|
];
|
|
4217
4218
|
var SUBGRAPH_OPEN = /^\s*subgraph\s+([A-Za-z][\w-]*)\s*(?:\[(.*)\])?\s*$/i;
|
|
4218
4219
|
var SUBGRAPH_END = /^\s*end\s*$/i;
|
|
4219
|
-
var DOCFLOW_EDGE_CONNECTOR = /^\s*-->(?:\|([^|]+)\|)?/;
|
|
4220
4220
|
var MERMAID_EDGE_CONNECTOR = /^\s*(?:-->|-\.->|==>|---)(?:\|([^|]+)\|)?/;
|
|
4221
4221
|
var DOCFLOW_PATH = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/;
|
|
4222
4222
|
var IDENTIFIER = /[A-Za-z_$][\w$]*/g;
|
|
@@ -4235,9 +4235,9 @@ var JS_WORDS = /* @__PURE__ */ new Set([
|
|
|
4235
4235
|
"undefined",
|
|
4236
4236
|
"var"
|
|
4237
4237
|
]);
|
|
4238
|
-
function parseAttributes(source,
|
|
4238
|
+
function parseAttributes(source, line, errors) {
|
|
4239
4239
|
const attributes = {};
|
|
4240
|
-
const validAttributes =
|
|
4240
|
+
const validAttributes = MERMAID_NODE_ATTRIBUTES;
|
|
4241
4241
|
let cursor = 0;
|
|
4242
4242
|
while (cursor < source.length) {
|
|
4243
4243
|
cursor += /^\s*/.exec(source.slice(cursor))?.[0].length ?? 0;
|
|
@@ -4246,21 +4246,21 @@ function parseAttributes(source, input) {
|
|
|
4246
4246
|
const match = ATTRIBUTE.exec(source);
|
|
4247
4247
|
if (!match) {
|
|
4248
4248
|
const fragment = source.slice(cursor).split(/\s+/)[0] ?? source.slice(cursor);
|
|
4249
|
-
|
|
4250
|
-
`Docflow annotation on line ${
|
|
4249
|
+
errors.push(
|
|
4250
|
+
`Docflow \`// @mermaid-node\` annotation on line ${line} has malformed attribute ${JSON.stringify(fragment)}. Use key:"value" pairs. Valid attributes: ${validAttributes.map((name2) => `"${name2}"`).join(", ")}.`
|
|
4251
4251
|
);
|
|
4252
4252
|
return null;
|
|
4253
4253
|
}
|
|
4254
4254
|
const name = match[1];
|
|
4255
4255
|
if (!validAttributes.includes(name)) {
|
|
4256
|
-
|
|
4257
|
-
`Docflow annotation on line ${
|
|
4256
|
+
errors.push(
|
|
4257
|
+
`Docflow \`// @mermaid-node\` annotation on line ${line} has unknown attribute "${name}". Valid attributes: ${validAttributes.map((option) => `"${option}"`).join(", ")}.`
|
|
4258
4258
|
);
|
|
4259
4259
|
return null;
|
|
4260
4260
|
}
|
|
4261
4261
|
if (Object.prototype.hasOwnProperty.call(attributes, name)) {
|
|
4262
|
-
|
|
4263
|
-
`Docflow annotation on line ${
|
|
4262
|
+
errors.push(
|
|
4263
|
+
`Docflow \`// @mermaid-node\` annotation on line ${line} repeats attribute "${name}". Write each attribute once.`
|
|
4264
4264
|
);
|
|
4265
4265
|
return null;
|
|
4266
4266
|
}
|
|
@@ -4349,7 +4349,7 @@ function parseShapedMermaidNodes(fragment) {
|
|
|
4349
4349
|
}
|
|
4350
4350
|
return nodes;
|
|
4351
4351
|
}
|
|
4352
|
-
function parseEdgeChain(line
|
|
4352
|
+
function parseEdgeChain(line) {
|
|
4353
4353
|
let remaining = line;
|
|
4354
4354
|
const first = parseMermaidNodeAtStart(remaining);
|
|
4355
4355
|
if (!first) return null;
|
|
@@ -4357,7 +4357,7 @@ function parseEdgeChain(line, syntax) {
|
|
|
4357
4357
|
const labels = [];
|
|
4358
4358
|
remaining = remaining.slice(first.length);
|
|
4359
4359
|
while (remaining.trim()) {
|
|
4360
|
-
const connector =
|
|
4360
|
+
const connector = MERMAID_EDGE_CONNECTOR.exec(remaining);
|
|
4361
4361
|
if (!connector) return null;
|
|
4362
4362
|
remaining = remaining.slice(connector[0].length);
|
|
4363
4363
|
const next = parseMermaidNodeAtStart(remaining);
|
|
@@ -4423,7 +4423,6 @@ function inferBindingIo(statement) {
|
|
|
4423
4423
|
};
|
|
4424
4424
|
}
|
|
4425
4425
|
var MERMAID_BLOCK = /\/\*\*\s*@mermaid(?:\s|\r?\n)([\s\S]*?)\*\//g;
|
|
4426
|
-
var LEGACY_BLOCK = /\/\*\*\s*@docflow(?:\s|\r?\n)([\s\S]*?)\*\//;
|
|
4427
4426
|
var BLOCK_EXPORT_HEADER = /^[A-Za-z_$][\w$-]*$/;
|
|
4428
4427
|
function cleanBlockBody(raw) {
|
|
4429
4428
|
return raw.split(/\r?\n/).map((line) => /^\s*\*/.test(line) ? line.replace(/^\s*\* ?/, "") : line).join("\n").trim();
|
|
@@ -4440,10 +4439,9 @@ function splitBlockExportHeader(body) {
|
|
|
4440
4439
|
};
|
|
4441
4440
|
}
|
|
4442
4441
|
function blockLabel(block) {
|
|
4443
|
-
|
|
4444
|
-
return block.authoredExportName ? `${tag} ${block.authoredExportName}` : tag;
|
|
4442
|
+
return block.authoredExportName ? `@mermaid ${block.authoredExportName}` : "@mermaid";
|
|
4445
4443
|
}
|
|
4446
|
-
function parseDocflowBlockGraph(mermaidSource,
|
|
4444
|
+
function parseDocflowBlockGraph(mermaidSource, errors) {
|
|
4447
4445
|
const docLines = mermaidSource.split(/\r?\n/).filter((line) => line.trim() && !line.trim().startsWith("%%"));
|
|
4448
4446
|
const direction = FLOW_START.exec(docLines[0] ?? "")?.[1]?.toUpperCase();
|
|
4449
4447
|
if (!direction) {
|
|
@@ -4459,11 +4457,9 @@ function parseDocflowBlockGraph(mermaidSource, syntax, errors) {
|
|
|
4459
4457
|
const sketchIds = /* @__PURE__ */ new Set();
|
|
4460
4458
|
const subgraphStack = [];
|
|
4461
4459
|
const subgraphIds = /* @__PURE__ */ new Set();
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
if (opened) subgraphIds.add(opened[1]);
|
|
4466
|
-
}
|
|
4460
|
+
for (const line of docLines.slice(1)) {
|
|
4461
|
+
const opened = SUBGRAPH_OPEN.exec(line);
|
|
4462
|
+
if (opened) subgraphIds.add(opened[1]);
|
|
4467
4463
|
}
|
|
4468
4464
|
const declaredIds = /* @__PURE__ */ new Set();
|
|
4469
4465
|
const addNodes = (fragment, line) => {
|
|
@@ -4505,34 +4501,32 @@ function parseDocflowBlockGraph(mermaidSource, syntax, errors) {
|
|
|
4505
4501
|
return sawDeclaration;
|
|
4506
4502
|
};
|
|
4507
4503
|
for (const line of docLines.slice(1)) {
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
if (
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
);
|
|
4528
|
-
}
|
|
4529
|
-
subgraphStack.pop();
|
|
4530
|
-
continue;
|
|
4504
|
+
const opened = SUBGRAPH_OPEN.exec(line);
|
|
4505
|
+
if (opened) {
|
|
4506
|
+
const id = opened[1];
|
|
4507
|
+
const rawLabel = opened[2]?.trim() ?? "";
|
|
4508
|
+
const label = rawLabel.length >= 2 && (rawLabel.startsWith('"') && rawLabel.endsWith('"') || rawLabel.startsWith("'") && rawLabel.endsWith("'")) ? rawLabel.slice(1, -1) : rawLabel;
|
|
4509
|
+
const subgraph = {
|
|
4510
|
+
id,
|
|
4511
|
+
label: label || id,
|
|
4512
|
+
memberIds: []
|
|
4513
|
+
};
|
|
4514
|
+
subgraphs.set(id, subgraph);
|
|
4515
|
+
subgraphStack.push(subgraph);
|
|
4516
|
+
continue;
|
|
4517
|
+
}
|
|
4518
|
+
if (SUBGRAPH_END.test(line)) {
|
|
4519
|
+
if (subgraphStack.length === 0) {
|
|
4520
|
+
errors.push(
|
|
4521
|
+
"Docflow has an `end` with no open `subgraph` above it. Every `end` closes exactly one `subgraph`."
|
|
4522
|
+
);
|
|
4531
4523
|
}
|
|
4524
|
+
subgraphStack.pop();
|
|
4525
|
+
continue;
|
|
4532
4526
|
}
|
|
4533
4527
|
let declaredOnThisLine = false;
|
|
4534
4528
|
const isDirective = /^\s*(?:direction|classDef|class|style|linkStyle|click)\b/i.test(line);
|
|
4535
|
-
if (
|
|
4529
|
+
if (isDirective) {
|
|
4536
4530
|
const sketch = SKETCH_CLASS.exec(line);
|
|
4537
4531
|
if (sketch) {
|
|
4538
4532
|
for (const id of sketch[1].split(",")) {
|
|
@@ -4543,21 +4537,19 @@ function parseDocflowBlockGraph(mermaidSource, syntax, errors) {
|
|
|
4543
4537
|
ignoredDirectives.push(line.trim());
|
|
4544
4538
|
}
|
|
4545
4539
|
}
|
|
4546
|
-
if (
|
|
4540
|
+
if (!isDirective) {
|
|
4547
4541
|
declaredOnThisLine = addNodes(line, line);
|
|
4548
4542
|
}
|
|
4549
|
-
const chain = parseEdgeChain(line
|
|
4543
|
+
const chain = parseEdgeChain(line);
|
|
4550
4544
|
if (!chain) {
|
|
4551
|
-
if (
|
|
4552
|
-
errors.push(`Unsupported docflow line: ${line.trim()}`);
|
|
4553
|
-
} else if (!isDirective && !declaredOnThisLine) {
|
|
4545
|
+
if (!isDirective && !declaredOnThisLine) {
|
|
4554
4546
|
errors.push(
|
|
4555
4547
|
`Docflow line is not an edge, a node declaration, or a directive this parser understands: ${line.trim()}`
|
|
4556
4548
|
);
|
|
4557
4549
|
}
|
|
4558
4550
|
continue;
|
|
4559
4551
|
}
|
|
4560
|
-
|
|
4552
|
+
addNodes(line, line);
|
|
4561
4553
|
for (const node of chain.nodes) {
|
|
4562
4554
|
if (subgraphIds.has(node.id)) continue;
|
|
4563
4555
|
const existing = nodes.get(node.id);
|
|
@@ -4616,24 +4608,18 @@ function materializeDocflow(block) {
|
|
|
4616
4608
|
}
|
|
4617
4609
|
function parsePlayDocflowFile(sourceCode) {
|
|
4618
4610
|
const errors = [];
|
|
4619
|
-
const rejectedNodeIds = /* @__PURE__ */ new Set();
|
|
4620
|
-
const bindings = parseDocflowBindings(sourceCode, errors, rejectedNodeIds);
|
|
4621
4611
|
const rawBlocks = [];
|
|
4622
4612
|
MERMAID_BLOCK.lastIndex = 0;
|
|
4623
4613
|
for (const match of sourceCode.matchAll(MERMAID_BLOCK)) {
|
|
4624
4614
|
rawBlocks.push({ syntax: "mermaid", body: cleanBlockBody(match[1]) });
|
|
4625
4615
|
}
|
|
4626
|
-
if (rawBlocks.length === 0) {
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
rawBlocks.push({ syntax: "docflow", body: cleanBlockBody(legacy[1]) });
|
|
4630
|
-
}
|
|
4631
|
-
}
|
|
4632
|
-
if (rawBlocks.length === 0) return { blocks: [], bindings, errors };
|
|
4616
|
+
if (rawBlocks.length === 0) return { blocks: [], bindings: [], errors };
|
|
4617
|
+
const rejectedNodeIds = /* @__PURE__ */ new Set();
|
|
4618
|
+
const bindings = parseDocflowBindings(sourceCode, errors, rejectedNodeIds);
|
|
4633
4619
|
const parsedBlocks = [];
|
|
4634
4620
|
for (const raw of rawBlocks) {
|
|
4635
4621
|
const { exportName, diagram } = splitBlockExportHeader(raw.body);
|
|
4636
|
-
const graph = parseDocflowBlockGraph(diagram,
|
|
4622
|
+
const graph = parseDocflowBlockGraph(diagram, errors);
|
|
4637
4623
|
if (!graph) continue;
|
|
4638
4624
|
parsedBlocks.push({
|
|
4639
4625
|
...graph,
|
|
@@ -4853,7 +4839,7 @@ function attachBindingsToBlocks(sourceCode, blocks, bindings, errors, rejectedNo
|
|
|
4853
4839
|
if (node.sketch || node.kind === "conceptual" || bound.has(node.id) || rejectedNodeIds.has(node.id))
|
|
4854
4840
|
continue;
|
|
4855
4841
|
errors.push(
|
|
4856
|
-
|
|
4842
|
+
`Docflow box "${node.id}" in \`${blockLabel(block)}\` points at nothing, so the canvas can only draw it and say nothing about it. Either put \`// @mermaid-node ${node.id}\` above the statement it runs, or \u2014 if this play has no such statement, because the work happens in another module or inside a loop \u2014 add it to a \`class ${node.id} sketch\` line in the block to declare it a sketch.`
|
|
4857
4843
|
);
|
|
4858
4844
|
}
|
|
4859
4845
|
}
|
|
@@ -4869,20 +4855,15 @@ function parseDocflowBindings(sourceCode, errors, rejectedNodeIds) {
|
|
|
4869
4855
|
const lines = sourceCode.split(/\r?\n/);
|
|
4870
4856
|
const bindings = [];
|
|
4871
4857
|
for (let index = 0; index < lines.length; index += 1) {
|
|
4872
|
-
PUT.lastIndex = 0;
|
|
4873
4858
|
MERMAID_NODE.lastIndex = 0;
|
|
4874
|
-
const
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
const annotatedId = mermaidMatch?.[1]?.trim();
|
|
4859
|
+
const mermaidMatch = MERMAID_NODE.exec(lines[index]) ?? null;
|
|
4860
|
+
if (!mermaidMatch) continue;
|
|
4861
|
+
const annotatedId = mermaidMatch[1]?.trim();
|
|
4878
4862
|
if (annotatedId) rejectedNodeIds.add(annotatedId);
|
|
4879
4863
|
const attributes = parseAttributes(
|
|
4880
|
-
mermaidMatch
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
annotation: mermaidMatch ? "@mermaid-node" : "put",
|
|
4884
|
-
errors
|
|
4885
|
-
}
|
|
4864
|
+
mermaidMatch[2] ?? "",
|
|
4865
|
+
index + 1,
|
|
4866
|
+
errors
|
|
4886
4867
|
);
|
|
4887
4868
|
if (!attributes) continue;
|
|
4888
4869
|
const id = (annotatedId ?? attributes.id)?.trim();
|
|
@@ -5210,6 +5191,113 @@ var SECRET_ENV_PATTERN = /\bprocess(?:\.env|\[['"]env['"]\])(?:\.|\[['"])([A-Z0-
|
|
|
5210
5191
|
var PRIVATE_KEY_PATTERN = /-----BEGIN (?:RSA |EC |OPENSSH |PGP )?PRIVATE KEY-----/;
|
|
5211
5192
|
var BEARER_LITERAL_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/i;
|
|
5212
5193
|
var ASSIGNMENT_SECRET_LITERAL_PATTERN = /\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*['"][^'"]{12,}['"]/i;
|
|
5194
|
+
var SECRET_AUTH_LITERAL_FINDING = "literal credential passed to ctx.secrets auth helper";
|
|
5195
|
+
function skipQuoted(source, start) {
|
|
5196
|
+
const quote = source[start];
|
|
5197
|
+
let index = start + 1;
|
|
5198
|
+
while (index < source.length) {
|
|
5199
|
+
if (source[index] === "\\") {
|
|
5200
|
+
index += 2;
|
|
5201
|
+
} else if (source[index] === quote) {
|
|
5202
|
+
return index + 1;
|
|
5203
|
+
} else {
|
|
5204
|
+
index += 1;
|
|
5205
|
+
}
|
|
5206
|
+
}
|
|
5207
|
+
return source.length;
|
|
5208
|
+
}
|
|
5209
|
+
function skipTrivia(source, start) {
|
|
5210
|
+
let index = start;
|
|
5211
|
+
while (index < source.length) {
|
|
5212
|
+
if (/\s/.test(source[index])) {
|
|
5213
|
+
index += 1;
|
|
5214
|
+
} else if (source.startsWith("//", index)) {
|
|
5215
|
+
index = source.indexOf("\n", index + 2);
|
|
5216
|
+
if (index === -1) return source.length;
|
|
5217
|
+
} else if (source.startsWith("/*", index)) {
|
|
5218
|
+
index = source.indexOf("*/", index + 2);
|
|
5219
|
+
if (index === -1) return source.length;
|
|
5220
|
+
index += 2;
|
|
5221
|
+
} else {
|
|
5222
|
+
return index;
|
|
5223
|
+
}
|
|
5224
|
+
}
|
|
5225
|
+
return index;
|
|
5226
|
+
}
|
|
5227
|
+
function consumeIdentifier(source, start, expected) {
|
|
5228
|
+
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;
|
|
5229
|
+
}
|
|
5230
|
+
function consumeMember(source, start, expected) {
|
|
5231
|
+
let index = skipTrivia(source, start);
|
|
5232
|
+
if (source[index] === ".") {
|
|
5233
|
+
return consumeIdentifier(source, skipTrivia(source, index + 1), expected);
|
|
5234
|
+
}
|
|
5235
|
+
if (source[index] !== "[") return void 0;
|
|
5236
|
+
index = skipTrivia(source, index + 1);
|
|
5237
|
+
if (source[index] !== "'" && source[index] !== '"') return void 0;
|
|
5238
|
+
const end = skipQuoted(source, index);
|
|
5239
|
+
if (source.slice(index + 1, end - 1) !== expected) return void 0;
|
|
5240
|
+
index = skipTrivia(source, end);
|
|
5241
|
+
return source[index] === "]" ? index + 1 : void 0;
|
|
5242
|
+
}
|
|
5243
|
+
function skipExpression(source, start) {
|
|
5244
|
+
let index = start;
|
|
5245
|
+
let depth = 0;
|
|
5246
|
+
while (index < source.length) {
|
|
5247
|
+
const char = source[index];
|
|
5248
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
5249
|
+
index = skipQuoted(source, index);
|
|
5250
|
+
continue;
|
|
5251
|
+
}
|
|
5252
|
+
if (source.startsWith("//", index) || source.startsWith("/*", index)) {
|
|
5253
|
+
index = skipTrivia(source, index);
|
|
5254
|
+
continue;
|
|
5255
|
+
}
|
|
5256
|
+
if (char === "(" || char === "[" || char === "{") {
|
|
5257
|
+
depth += 1;
|
|
5258
|
+
} else if (char === ")" || char === "]" || char === "}") {
|
|
5259
|
+
if (depth === 0) return index;
|
|
5260
|
+
depth -= 1;
|
|
5261
|
+
} else if (char === "," && depth === 0) {
|
|
5262
|
+
return index;
|
|
5263
|
+
}
|
|
5264
|
+
index += 1;
|
|
5265
|
+
}
|
|
5266
|
+
return index;
|
|
5267
|
+
}
|
|
5268
|
+
function isDirectStringLiteral(source, start) {
|
|
5269
|
+
const quote = source[start];
|
|
5270
|
+
if (quote !== "'" && quote !== '"' && quote !== "`") return false;
|
|
5271
|
+
const end = skipQuoted(source, start);
|
|
5272
|
+
return quote !== "`" || !source.slice(start, end).includes("${");
|
|
5273
|
+
}
|
|
5274
|
+
function hasLiteralSecretAuthCredential(source) {
|
|
5275
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
5276
|
+
const char = source[index];
|
|
5277
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
5278
|
+
index = skipQuoted(source, index) - 1;
|
|
5279
|
+
continue;
|
|
5280
|
+
}
|
|
5281
|
+
if (source.startsWith("//", index) || source.startsWith("/*", index)) {
|
|
5282
|
+
index = skipTrivia(source, index) - 1;
|
|
5283
|
+
continue;
|
|
5284
|
+
}
|
|
5285
|
+
const afterCtx = consumeIdentifier(source, index, "ctx");
|
|
5286
|
+
if (!afterCtx) continue;
|
|
5287
|
+
const afterSecrets = consumeMember(source, afterCtx, "secrets");
|
|
5288
|
+
if (!afterSecrets) continue;
|
|
5289
|
+
const afterHeader = consumeMember(source, afterSecrets, "header");
|
|
5290
|
+
const helper = afterHeader ? "header" : "bearer";
|
|
5291
|
+
const afterHelper = afterHeader ?? consumeMember(source, afterSecrets, "bearer");
|
|
5292
|
+
if (!afterHelper) continue;
|
|
5293
|
+
const afterOpen = skipTrivia(source, afterHelper);
|
|
5294
|
+
if (source[afterOpen] !== "(") continue;
|
|
5295
|
+
const firstArgument = skipTrivia(source, afterOpen + 1);
|
|
5296
|
+
const credential = helper === "header" ? skipTrivia(source, skipExpression(source, firstArgument) + 1) : firstArgument;
|
|
5297
|
+
if (isDirectStringLiteral(source, credential)) return true;
|
|
5298
|
+
}
|
|
5299
|
+
return false;
|
|
5300
|
+
}
|
|
5213
5301
|
function collectInlineSecretFindings(sourceCode) {
|
|
5214
5302
|
const findings = [];
|
|
5215
5303
|
for (const match of sourceCode.matchAll(SECRET_ENV_PATTERN)) {
|
|
@@ -5221,6 +5309,9 @@ function collectInlineSecretFindings(sourceCode) {
|
|
|
5221
5309
|
if (ASSIGNMENT_SECRET_LITERAL_PATTERN.test(sourceCode)) {
|
|
5222
5310
|
findings.push("secret-looking assignment literal");
|
|
5223
5311
|
}
|
|
5312
|
+
if (hasLiteralSecretAuthCredential(sourceCode)) {
|
|
5313
|
+
findings.push(SECRET_AUTH_LITERAL_FINDING);
|
|
5314
|
+
}
|
|
5224
5315
|
return [...new Set(findings)];
|
|
5225
5316
|
}
|
|
5226
5317
|
function validatePlaySourceHasNoInlineSecrets(input) {
|
|
@@ -5231,7 +5322,7 @@ function validatePlaySourceHasNoInlineSecrets(input) {
|
|
|
5231
5322
|
`Play source ${input.filePath} appears to contain inline secret material: ${[
|
|
5232
5323
|
...new Set(findings)
|
|
5233
5324
|
].join(", ")}.`,
|
|
5234
|
-
|
|
5325
|
+
`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").`
|
|
5235
5326
|
].join(" ")
|
|
5236
5327
|
);
|
|
5237
5328
|
}
|