deepline 0.1.222 → 0.1.224
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/apps/play-runner-workers/src/coordinator-entry.ts +396 -176
- package/dist/bundling-sources/apps/play-runner-workers/src/dedup-do.ts +6 -3
- package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +210 -105
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/shared_libs/observability/node-tracing.ts +125 -0
- package/dist/bundling-sources/shared_libs/observability/redaction.ts +94 -0
- package/dist/bundling-sources/shared_libs/observability/telemetry.ts +414 -0
- package/dist/bundling-sources/shared_libs/observability/tracing.ts +93 -0
- package/dist/bundling-sources/shared_libs/observability/worker-telemetry.ts +119 -0
- package/dist/bundling-sources/shared_libs/play-runtime/secret-redaction.ts +32 -14
- package/dist/cli/index.js +220 -164
- package/dist/cli/index.mjs +220 -164
- package/dist/index.js +72 -56
- package/dist/index.mjs +72 -56
- package/package.json +1 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SpanKind,
|
|
3
|
+
SpanStatusCode,
|
|
4
|
+
trace,
|
|
5
|
+
type Attributes,
|
|
6
|
+
type Span,
|
|
7
|
+
} from '@opentelemetry/api';
|
|
8
|
+
|
|
9
|
+
type PrimitiveAttribute = string | number | boolean | null | undefined;
|
|
10
|
+
|
|
11
|
+
type TraceAttributes = Record<string, PrimitiveAttribute>;
|
|
12
|
+
|
|
13
|
+
function normalizeAttributes(
|
|
14
|
+
attributes: TraceAttributes | undefined,
|
|
15
|
+
): Attributes | undefined {
|
|
16
|
+
if (!attributes) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const normalized: Attributes = {};
|
|
21
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
22
|
+
if (value === undefined || value === null) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (
|
|
26
|
+
typeof value === 'string' ||
|
|
27
|
+
typeof value === 'number' ||
|
|
28
|
+
typeof value === 'boolean'
|
|
29
|
+
) {
|
|
30
|
+
normalized[key] = value;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function recordError(span: Span, error: unknown): void {
|
|
37
|
+
if (error instanceof Error) {
|
|
38
|
+
span.recordException(error);
|
|
39
|
+
span.setStatus({
|
|
40
|
+
code: SpanStatusCode.ERROR,
|
|
41
|
+
message: error.message,
|
|
42
|
+
});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const message = String(error);
|
|
47
|
+
span.recordException({ name: 'Error', message });
|
|
48
|
+
span.setStatus({
|
|
49
|
+
code: SpanStatusCode.ERROR,
|
|
50
|
+
message,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function withActiveSpan<T>(
|
|
55
|
+
name: string,
|
|
56
|
+
options: {
|
|
57
|
+
tracer?: string;
|
|
58
|
+
kind?: SpanKind;
|
|
59
|
+
attributes?: TraceAttributes;
|
|
60
|
+
},
|
|
61
|
+
fn: (span: Span) => Promise<T> | T,
|
|
62
|
+
): Promise<T> {
|
|
63
|
+
const tracer = trace.getTracer(options.tracer ?? 'deepline');
|
|
64
|
+
return await tracer.startActiveSpan(
|
|
65
|
+
name,
|
|
66
|
+
{
|
|
67
|
+
kind: options.kind,
|
|
68
|
+
attributes: normalizeAttributes(options.attributes),
|
|
69
|
+
},
|
|
70
|
+
async (span) => {
|
|
71
|
+
try {
|
|
72
|
+
const result = await fn(span);
|
|
73
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
74
|
+
return result;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
recordError(span, error);
|
|
77
|
+
throw error;
|
|
78
|
+
} finally {
|
|
79
|
+
span.end();
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function setSpanAttributes(
|
|
86
|
+
span: Span,
|
|
87
|
+
attributes: TraceAttributes,
|
|
88
|
+
): void {
|
|
89
|
+
const normalized = normalizeAttributes(attributes);
|
|
90
|
+
if (normalized) {
|
|
91
|
+
span.setAttributes(normalized);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
export type TelemetryScalar = string | number | boolean | null | undefined;
|
|
2
|
+
export type TelemetryFields = Readonly<Record<string, unknown>>;
|
|
3
|
+
|
|
4
|
+
type TelemetryContext = {
|
|
5
|
+
orgId?: string | null;
|
|
6
|
+
playName?: string | null;
|
|
7
|
+
requestId?: string | null;
|
|
8
|
+
runId?: string | null;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type TelemetryEvent = {
|
|
12
|
+
telemetrySchemaVersion: 1;
|
|
13
|
+
timestamp: string;
|
|
14
|
+
service: string;
|
|
15
|
+
component: string | null;
|
|
16
|
+
event: string;
|
|
17
|
+
tag: string;
|
|
18
|
+
telemetryKind: 'log';
|
|
19
|
+
telemetryLevel: 'info' | 'warn' | 'error';
|
|
20
|
+
errorType?: string;
|
|
21
|
+
errorMessage?: string;
|
|
22
|
+
} & TelemetryContext &
|
|
23
|
+
Record<string, TelemetryScalar>;
|
|
24
|
+
|
|
25
|
+
type TelemetryAdapter = { emit(event: TelemetryEvent): void | Promise<void> };
|
|
26
|
+
type TelemetryOptions = { tag?: string };
|
|
27
|
+
type WorkerTelemetryInput = {
|
|
28
|
+
service: string;
|
|
29
|
+
component?: string;
|
|
30
|
+
context?: TelemetryContext;
|
|
31
|
+
adapter?: TelemetryAdapter;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type WorkerTelemetry = {
|
|
35
|
+
child(input: {
|
|
36
|
+
component?: string;
|
|
37
|
+
context?: TelemetryContext;
|
|
38
|
+
}): WorkerTelemetry;
|
|
39
|
+
info(
|
|
40
|
+
event: string,
|
|
41
|
+
fields?: TelemetryFields,
|
|
42
|
+
options?: TelemetryOptions,
|
|
43
|
+
): Promise<void>;
|
|
44
|
+
warn(
|
|
45
|
+
event: string,
|
|
46
|
+
fields?: TelemetryFields,
|
|
47
|
+
options?: TelemetryOptions,
|
|
48
|
+
): Promise<void>;
|
|
49
|
+
error(
|
|
50
|
+
event: string,
|
|
51
|
+
error?: unknown,
|
|
52
|
+
fields?: TelemetryFields,
|
|
53
|
+
options?: TelemetryOptions,
|
|
54
|
+
): Promise<void>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Small, Worker-safe implementation of the canonical telemetry envelope.
|
|
59
|
+
* It intentionally omits server-only recursive normalization and adapters so
|
|
60
|
+
* it can remain in the size-capped dynamic play bundle.
|
|
61
|
+
*/
|
|
62
|
+
export function createWorkerTelemetry(
|
|
63
|
+
input: WorkerTelemetryInput,
|
|
64
|
+
): WorkerTelemetry {
|
|
65
|
+
const service = input.service.trim();
|
|
66
|
+
const component = input.component?.trim() || null;
|
|
67
|
+
const context = input.context ?? {};
|
|
68
|
+
const adapter = input.adapter ?? {
|
|
69
|
+
emit: (event: TelemetryEvent) =>
|
|
70
|
+
console[event.telemetryLevel](JSON.stringify(event)),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
async function emit(
|
|
74
|
+
level: TelemetryEvent['telemetryLevel'],
|
|
75
|
+
event: string,
|
|
76
|
+
fields?: TelemetryFields,
|
|
77
|
+
options?: TelemetryOptions,
|
|
78
|
+
error?: unknown,
|
|
79
|
+
): Promise<void> {
|
|
80
|
+
try {
|
|
81
|
+
await adapter.emit({
|
|
82
|
+
telemetrySchemaVersion: 1,
|
|
83
|
+
timestamp: new Date().toISOString(),
|
|
84
|
+
service,
|
|
85
|
+
component,
|
|
86
|
+
event,
|
|
87
|
+
tag: options?.tag?.trim() || `[${event}]`,
|
|
88
|
+
...context,
|
|
89
|
+
...(fields as Record<string, TelemetryScalar>),
|
|
90
|
+
telemetryKind: 'log',
|
|
91
|
+
telemetryLevel: level,
|
|
92
|
+
...(error == null
|
|
93
|
+
? {}
|
|
94
|
+
: {
|
|
95
|
+
errorType: error instanceof Error ? error.name : 'Error',
|
|
96
|
+
errorMessage:
|
|
97
|
+
error instanceof Error ? error.message : String(error),
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
} catch {
|
|
101
|
+
// Never make workflow execution depend on telemetry delivery.
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
child(child) {
|
|
107
|
+
return createWorkerTelemetry({
|
|
108
|
+
service,
|
|
109
|
+
component: child.component ?? component ?? undefined,
|
|
110
|
+
context: { ...context, ...child.context },
|
|
111
|
+
adapter,
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
info: (event, fields, options) => emit('info', event, fields, options),
|
|
115
|
+
warn: (event, fields, options) => emit('warn', event, fields, options),
|
|
116
|
+
error: (event, error, fields, options) =>
|
|
117
|
+
emit('error', event, fields, options, error),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -1,37 +1,47 @@
|
|
|
1
1
|
export const SECRET_REDACTION_PLACEHOLDER = '[REDACTED_SECRET]';
|
|
2
2
|
|
|
3
3
|
const BEARER_TOKEN_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/g;
|
|
4
|
-
const
|
|
5
|
-
|
|
4
|
+
const BASIC_CREDENTIAL_RE = /\bBasic\s+[A-Za-z0-9+/]{4,}={0,2}\b/g;
|
|
5
|
+
const JWT_RE = /\b[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{1,}\.[A-Za-z0-9_-]{8,}\b/g;
|
|
6
6
|
const PRIVATE_KEY_RE =
|
|
7
7
|
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g;
|
|
8
|
+
const SENSITIVE_FIELD_NAME =
|
|
9
|
+
'authorization|cookie|password|(?:client[_-]?)?secret|(?:access|refresh)[_-]?token|(?:x[_-]?)?api[_-]?key|private[_-]?key|token|credentials';
|
|
8
10
|
// Only redact self-identifying credential prefixes here. Generic words such as
|
|
9
11
|
// `key`, `token`, and `secret` are common in harmless play/run slugs (for
|
|
10
12
|
// example `absurd-key-rotation-check`) and need an assignment context before
|
|
11
13
|
// they are sensitive; HIGH_ENTROPY_ASSIGNMENT_RE handles that case below.
|
|
12
14
|
const COMMON_SECRET_RE =
|
|
13
|
-
/\b(?:(?:
|
|
15
|
+
/\b(?:[prs]k_(?:live|test)_|(?:pat|ghp|github_pat)_|xox[baprs]-)[A-Za-z0-9_./+=:-]{12,}\b/gi;
|
|
14
16
|
const GENERIC_PREFIXED_SECRET_RE =
|
|
15
17
|
/\b(?:key|token|secret|api[_-]?key)_[A-Za-z0-9_./+=:-]{12,}\b/gi;
|
|
16
18
|
const URL_SECRET_VALUE_RE =
|
|
17
|
-
/\b(?:
|
|
19
|
+
/\b(?:[spr]k|pat|ghp|github_pat|xox)[A-Za-z0-9_./+=:-]{12,}\b/i;
|
|
18
20
|
const GENERIC_SECRET_ASSIGNMENT_RE =
|
|
19
21
|
/\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*["']?[^"',\s]{12,}["']?/gi;
|
|
20
22
|
const HIGH_ENTROPY_ASSIGNMENT_RE =
|
|
21
|
-
/\b(?:api[_-]?key|token|secret|password|authorization|access
|
|
23
|
+
/\b(?:api[_-]?key|token|secret|password|authorization|(?:access|refresh)[_-]?token)\b\s*[:=]\s*["']?[^"',\s]{16,}["']?/gi;
|
|
22
24
|
const SENSITIVE_URL_PARAM_RE =
|
|
23
25
|
/[?#&](?:\w*(?:key|token)|secret|password|authorization|credential|signature|sig)(?:=|$)/i;
|
|
26
|
+
const SENSITIVE_FIELD_NAME_RE = new RegExp(
|
|
27
|
+
`(^|[._-])(?:${SENSITIVE_FIELD_NAME})$`,
|
|
28
|
+
'i',
|
|
29
|
+
);
|
|
30
|
+
const SERIALIZED_SECRET_FIELD_RE = new RegExp(
|
|
31
|
+
`"(${SENSITIVE_FIELD_NAME})"\\s*:\\s*(?:"[^"\\\\]*"|\\{[^{}]*\\})`,
|
|
32
|
+
'gi',
|
|
33
|
+
);
|
|
24
34
|
|
|
25
35
|
function escapeRegExp(value: string): string {
|
|
26
36
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
27
37
|
}
|
|
28
38
|
|
|
29
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
30
|
-
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
39
|
export function redactSecretLikeString(value: string): string {
|
|
34
|
-
const
|
|
40
|
+
const serializedRedacted = value.replace(
|
|
41
|
+
SERIALIZED_SECRET_FIELD_RE,
|
|
42
|
+
`"$1":"${SECRET_REDACTION_PLACEHOLDER}"`,
|
|
43
|
+
);
|
|
44
|
+
const trimmed = serializedRedacted.trim();
|
|
35
45
|
if (/^https?:\/\/\S+$/i.test(trimmed)) {
|
|
36
46
|
if (
|
|
37
47
|
/^https?:\/\/[^/?#@]+@/i.test(trimmed) ||
|
|
@@ -39,12 +49,15 @@ export function redactSecretLikeString(value: string): string {
|
|
|
39
49
|
) {
|
|
40
50
|
return SECRET_REDACTION_PLACEHOLDER;
|
|
41
51
|
}
|
|
42
|
-
if (!URL_SECRET_VALUE_RE.test(
|
|
52
|
+
if (!URL_SECRET_VALUE_RE.test(serializedRedacted)) {
|
|
53
|
+
return serializedRedacted;
|
|
54
|
+
}
|
|
43
55
|
}
|
|
44
56
|
|
|
45
|
-
return
|
|
57
|
+
return serializedRedacted
|
|
46
58
|
.replace(PRIVATE_KEY_RE, SECRET_REDACTION_PLACEHOLDER)
|
|
47
59
|
.replace(BEARER_TOKEN_RE, `Bearer ${SECRET_REDACTION_PLACEHOLDER}`)
|
|
60
|
+
.replace(BASIC_CREDENTIAL_RE, `Basic ${SECRET_REDACTION_PLACEHOLDER}`)
|
|
48
61
|
.replace(JWT_RE, SECRET_REDACTION_PLACEHOLDER)
|
|
49
62
|
.replace(COMMON_SECRET_RE, SECRET_REDACTION_PLACEHOLDER)
|
|
50
63
|
.replace(GENERIC_PREFIXED_SECRET_RE, SECRET_REDACTION_PLACEHOLDER)
|
|
@@ -99,9 +112,14 @@ export function createSecretRedactionContext(
|
|
|
99
112
|
function redact<T>(value: T): T {
|
|
100
113
|
if (typeof value === 'string') return redactString(value) as T;
|
|
101
114
|
if (Array.isArray(value)) return value.map((entry) => redact(entry)) as T;
|
|
102
|
-
if (
|
|
115
|
+
if (value && typeof value === 'object') {
|
|
103
116
|
return Object.fromEntries(
|
|
104
|
-
Object.entries(value).map(([key, entry]) => [
|
|
117
|
+
Object.entries(value).map(([key, entry]) => [
|
|
118
|
+
key,
|
|
119
|
+
SENSITIVE_FIELD_NAME_RE.test(key)
|
|
120
|
+
? SECRET_REDACTION_PLACEHOLDER
|
|
121
|
+
: redact(entry),
|
|
122
|
+
]),
|
|
105
123
|
) as T;
|
|
106
124
|
}
|
|
107
125
|
return value;
|