deepline 0.2.18 → 0.2.20
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/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/secret-guardrails.ts +0 -42
- package/dist/bundling-sources/shared_libs/plays/static-pipeline-storage-codec.ts +27 -0
- package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +568 -13
- package/dist/cli/index.js +5 -31
- package/dist/cli/index.mjs +5 -31
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/plays/bundle-play-file.mjs +0 -30
- package/package.json +1 -1
|
@@ -160,7 +160,7 @@ export const SDK_RELEASE = {
|
|
|
160
160
|
// 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
|
|
161
161
|
// exposed storage-dependent synchronous access. This deliberate minor
|
|
162
162
|
// release keeps lazy paging semantics independent of row residency.
|
|
163
|
-
version: '0.2.
|
|
163
|
+
version: '0.2.20',
|
|
164
164
|
contracts: {
|
|
165
165
|
api: {
|
|
166
166
|
name: 'sdk-http-api',
|
|
@@ -5,34 +5,6 @@ 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 HIGH_ENTROPY_LITERAL_PATTERN = /['"]([A-Za-z0-9+/=_-]{32,})['"]/g;
|
|
9
|
-
const UUID_IDENTIFIER_PATTERN =
|
|
10
|
-
/^((?:[A-Za-z0-9]+[-_])*)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
|
|
11
|
-
const BOOTSTRAP_RESOURCE_IDENTIFIER_PATTERN =
|
|
12
|
-
/^bootstrap-[0-9a-f]{32}(?:\/[a-z0-9][a-z0-9_-]{0,127})?$/i;
|
|
13
|
-
const SECRET_LABEL_PATTERN =
|
|
14
|
-
/(?:^|[-_])(?:api|auth|access|secret|token|key|password|credential|bearer|sk|pk|live)(?:[-_]|$)/i;
|
|
15
|
-
|
|
16
|
-
function shannonEntropy(value: string): number {
|
|
17
|
-
const counts = new Map<string, number>();
|
|
18
|
-
for (const char of value) counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
19
|
-
return [...counts.values()].reduce((entropy, count) => {
|
|
20
|
-
const p = count / value.length;
|
|
21
|
-
return entropy - p * Math.log2(p);
|
|
22
|
-
}, 0);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function isNonSecretUuidIdentifier(value: string): boolean {
|
|
26
|
-
const match = UUID_IDENTIFIER_PATTERN.exec(value);
|
|
27
|
-
if (!match) return false;
|
|
28
|
-
const label = match[1] ?? '';
|
|
29
|
-
return !SECRET_LABEL_PATTERN.test(label);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function isNonSecretBootstrapResourceIdentifier(value: string): boolean {
|
|
33
|
-
return BOOTSTRAP_RESOURCE_IDENTIFIER_PATTERN.test(value);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
8
|
/**
|
|
37
9
|
* Returns the inline-secret findings in a string (empty if none). The throwing
|
|
38
10
|
* validator below and the workflows→plays migration validator both call this so
|
|
@@ -50,20 +22,6 @@ export function collectInlineSecretFindings(sourceCode: string): string[] {
|
|
|
50
22
|
if (ASSIGNMENT_SECRET_LITERAL_PATTERN.test(sourceCode)) {
|
|
51
23
|
findings.push('secret-looking assignment literal');
|
|
52
24
|
}
|
|
53
|
-
for (const match of sourceCode.matchAll(HIGH_ENTROPY_LITERAL_PATTERN)) {
|
|
54
|
-
const literal = match[1] ?? '';
|
|
55
|
-
// UUID-bearing resource names are structured identifiers, not opaque
|
|
56
|
-
// credentials. Keep secret-looking labels on the conservative path.
|
|
57
|
-
if (isNonSecretUuidIdentifier(literal)) continue;
|
|
58
|
-
// Named CI orgs use a deterministic public `bootstrap-<sha256-prefix>`
|
|
59
|
-
// slug. It is an address, not a credential, and owner-qualified ctx.runPlay
|
|
60
|
-
// references must embed it as a literal for static child resolution.
|
|
61
|
-
if (isNonSecretBootstrapResourceIdentifier(literal)) continue;
|
|
62
|
-
if (literal.length >= 40 && shannonEntropy(literal) >= 4.2) {
|
|
63
|
-
findings.push('high-entropy string literal');
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
25
|
return [...new Set(findings)];
|
|
68
26
|
}
|
|
69
27
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createStaticPipelineStorageFields,
|
|
3
|
+
createStoredStaticPipelineV2,
|
|
4
|
+
hydrateStoredStaticPipelineV2,
|
|
5
|
+
readStoredStaticPipeline,
|
|
6
|
+
} from './static-pipeline';
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
isStoredStaticPipelineV2IntegrityError,
|
|
10
|
+
STATIC_PIPELINE_V2_INTEGRITY_ERROR_CODE,
|
|
11
|
+
STATIC_PIPELINE_V2_INTEGRITY_USER_MESSAGE,
|
|
12
|
+
StoredStaticPipelineV2IntegrityError,
|
|
13
|
+
} from './static-pipeline';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Node- and Convex-agnostic codec for the durable static-pipeline format.
|
|
17
|
+
*
|
|
18
|
+
* `flatten` is the only supported recursive graph -> flat V2 conversion.
|
|
19
|
+
* `hydrate` is its strict inverse. `storageFields` creates the complete V2 plus
|
|
20
|
+
* the bounded V1 compatibility mirror. `read` handles legacy rows with no V2.
|
|
21
|
+
*/
|
|
22
|
+
export const staticPipelineStorageCodec = Object.freeze({
|
|
23
|
+
flatten: createStoredStaticPipelineV2,
|
|
24
|
+
hydrate: hydrateStoredStaticPipelineV2,
|
|
25
|
+
storageFields: createStaticPipelineStorageFields,
|
|
26
|
+
read: readStoredStaticPipeline,
|
|
27
|
+
});
|
|
@@ -43,6 +43,45 @@ export interface PlayStaticPipeline {
|
|
|
43
43
|
sheetContractErrors?: string[];
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Durable V2 representation for static pipelines. Convex documents have a
|
|
48
|
+
* finite nesting limit, so this stores the recursive graph as flat records.
|
|
49
|
+
* The V1 `staticPipeline` field remains a small compatibility projection for
|
|
50
|
+
* legacy readers; V2 is rehydrated before current readers consume it.
|
|
51
|
+
*/
|
|
52
|
+
export interface StoredStaticPipelineV2 {
|
|
53
|
+
version: 2;
|
|
54
|
+
rootPipelineId: string;
|
|
55
|
+
pipelines: StoredStaticPipelineV2Pipeline[];
|
|
56
|
+
nodes: StoredStaticPipelineV2Node[];
|
|
57
|
+
branches: StoredStaticPipelineV2Branch[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface StoredStaticPipelineV2Pipeline {
|
|
61
|
+
id: string;
|
|
62
|
+
parentNodeId?: string;
|
|
63
|
+
value: Record<string, unknown>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface StoredStaticPipelineV2Node {
|
|
67
|
+
id: string;
|
|
68
|
+
pipelineId: string;
|
|
69
|
+
parentNodeId?: string;
|
|
70
|
+
branchId?: string;
|
|
71
|
+
container: 'stages' | 'substeps' | 'steps' | 'branch_steps';
|
|
72
|
+
index: number;
|
|
73
|
+
nestedPipelineId?: string;
|
|
74
|
+
value: Record<string, unknown>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface StoredStaticPipelineV2Branch {
|
|
78
|
+
id: string;
|
|
79
|
+
nodeId: string;
|
|
80
|
+
index: number;
|
|
81
|
+
label: string;
|
|
82
|
+
condition?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
46
85
|
function dedupeStaticFieldNames(
|
|
47
86
|
fields: Array<string | null | undefined>,
|
|
48
87
|
): string[] {
|
|
@@ -99,10 +138,7 @@ export function deriveStaticPipelineEntryInputFields(
|
|
|
99
138
|
}
|
|
100
139
|
|
|
101
140
|
export type PlaySheetColumnSource =
|
|
102
|
-
| '
|
|
103
|
-
| 'datasetColumn'
|
|
104
|
-
| 'waterfallStep'
|
|
105
|
-
| 'childPlayColumn';
|
|
141
|
+
'input' | 'datasetColumn' | 'waterfallStep' | 'childPlayColumn';
|
|
106
142
|
|
|
107
143
|
export interface PlaySheetColumnContract {
|
|
108
144
|
id: string;
|
|
@@ -546,6 +582,520 @@ export function truncateStaticPipelineForRuntimeContract(
|
|
|
546
582
|
});
|
|
547
583
|
}
|
|
548
584
|
|
|
585
|
+
function asStaticRecord(value: unknown): Record<string, unknown> | null {
|
|
586
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
587
|
+
? (value as Record<string, unknown>)
|
|
588
|
+
: null;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function storageSafeNodeValue(
|
|
592
|
+
value: Record<string, unknown>,
|
|
593
|
+
): Record<string, unknown> {
|
|
594
|
+
const {
|
|
595
|
+
branches: _branches,
|
|
596
|
+
pipeline: _pipeline,
|
|
597
|
+
steps: _steps,
|
|
598
|
+
...rest
|
|
599
|
+
} = value;
|
|
600
|
+
const out: Record<string, unknown> = { ...rest };
|
|
601
|
+
|
|
602
|
+
// `columns[].producers[].substep` can itself contain a recursive static
|
|
603
|
+
// graph. The sheet contract is the durable interface for columns, while this
|
|
604
|
+
// compact metadata keeps their labels available to older graph views.
|
|
605
|
+
if (Array.isArray(value.columns)) {
|
|
606
|
+
out.columns = value.columns.flatMap((column) => {
|
|
607
|
+
const record = asStaticRecord(column);
|
|
608
|
+
if (!record) return [];
|
|
609
|
+
return [
|
|
610
|
+
omitUndefinedProperties({
|
|
611
|
+
id: record.id,
|
|
612
|
+
source: record.source,
|
|
613
|
+
sqlName: record.sqlName,
|
|
614
|
+
}),
|
|
615
|
+
];
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
if (Array.isArray(value.callPath)) out.callPath = [...value.callPath];
|
|
619
|
+
if (Array.isArray(value.inputFields))
|
|
620
|
+
out.inputFields = [...value.inputFields];
|
|
621
|
+
if (Array.isArray(value.rowKeyFields))
|
|
622
|
+
out.rowKeyFields = [...value.rowKeyFields];
|
|
623
|
+
if (Array.isArray(value.outputFields))
|
|
624
|
+
out.outputFields = [...value.outputFields];
|
|
625
|
+
if (Array.isArray(value.waterfallIds))
|
|
626
|
+
out.waterfallIds = [...value.waterfallIds];
|
|
627
|
+
if (Array.isArray(value.steps) && value.type === 'waterfall') {
|
|
628
|
+
out.steps = value.steps.flatMap((step) => {
|
|
629
|
+
const record = asStaticRecord(step);
|
|
630
|
+
if (!record) return [];
|
|
631
|
+
return [
|
|
632
|
+
omitUndefinedProperties({
|
|
633
|
+
id: record.id,
|
|
634
|
+
kind: record.kind,
|
|
635
|
+
toolId: record.toolId,
|
|
636
|
+
}),
|
|
637
|
+
];
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
if (asStaticRecord(value.sourceRange)) {
|
|
641
|
+
out.sourceRange = { ...(value.sourceRange as Record<string, unknown>) };
|
|
642
|
+
}
|
|
643
|
+
if (asStaticRecord(value.sheetContract)) {
|
|
644
|
+
out.sheetContract = cloneStorageSafeSheetContract(
|
|
645
|
+
value.sheetContract as PlaySheetContract,
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
return stripUndefinedDeep(out);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function storageSafePipelineValue(
|
|
652
|
+
pipeline: PlayStaticPipeline,
|
|
653
|
+
): Record<string, unknown> {
|
|
654
|
+
const { stages: _stages, substeps: _substeps, ...value } = pipeline;
|
|
655
|
+
return stripUndefinedDeep(
|
|
656
|
+
omitUndefinedProperties({
|
|
657
|
+
...value,
|
|
658
|
+
inputFields: pipeline.inputFields ? [...pipeline.inputFields] : undefined,
|
|
659
|
+
rowKeyFields: pipeline.rowKeyFields
|
|
660
|
+
? [...pipeline.rowKeyFields]
|
|
661
|
+
: undefined,
|
|
662
|
+
fields: [...(pipeline.fields ?? [])],
|
|
663
|
+
returnFields: pipeline.returnFields?.map((field) => ({ ...field })),
|
|
664
|
+
sheetContract: cloneStorageSafeSheetContract(pipeline.sheetContract),
|
|
665
|
+
sheetContractErrors: pipeline.sheetContractErrors
|
|
666
|
+
? [...pipeline.sheetContractErrors]
|
|
667
|
+
: undefined,
|
|
668
|
+
}),
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/** Creates the complete flat V2 graph from a bounded, storage-safe pipeline. */
|
|
673
|
+
export function createStoredStaticPipelineV2(
|
|
674
|
+
pipeline: PlayStaticPipeline | null | undefined,
|
|
675
|
+
): StoredStaticPipelineV2 | null | undefined {
|
|
676
|
+
if (pipeline === null) return null;
|
|
677
|
+
if (pipeline === undefined) return undefined;
|
|
678
|
+
|
|
679
|
+
const bounded = truncateStaticPipelineForStorage(pipeline, {
|
|
680
|
+
maxEmbeddedPlayCallPipelineDepth: Number.POSITIVE_INFINITY,
|
|
681
|
+
maxStoredSubstepDepth: Number.POSITIVE_INFINITY,
|
|
682
|
+
});
|
|
683
|
+
if (!bounded) {
|
|
684
|
+
throw new Error(
|
|
685
|
+
'Static pipeline truncation unexpectedly returned no graph',
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const result: StoredStaticPipelineV2 = {
|
|
690
|
+
version: 2,
|
|
691
|
+
rootPipelineId: 'pipeline:0',
|
|
692
|
+
pipelines: [],
|
|
693
|
+
nodes: [],
|
|
694
|
+
branches: [],
|
|
695
|
+
};
|
|
696
|
+
let nextPipeline = 0;
|
|
697
|
+
let nextNode = 0;
|
|
698
|
+
let nextBranch = 0;
|
|
699
|
+
|
|
700
|
+
const appendPipeline = (
|
|
701
|
+
current: PlayStaticPipeline,
|
|
702
|
+
parentNodeId?: string,
|
|
703
|
+
): string => {
|
|
704
|
+
const pipelineId = `pipeline:${nextPipeline++}`;
|
|
705
|
+
result.pipelines.push({
|
|
706
|
+
id: pipelineId,
|
|
707
|
+
...(parentNodeId ? { parentNodeId } : {}),
|
|
708
|
+
value: storageSafePipelineValue(current),
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
const appendNodes = (
|
|
712
|
+
entries: unknown[],
|
|
713
|
+
container: StoredStaticPipelineV2Node['container'],
|
|
714
|
+
parentNodeId?: string,
|
|
715
|
+
branchId?: string,
|
|
716
|
+
) => {
|
|
717
|
+
entries.forEach((entry, index) => {
|
|
718
|
+
const record = asStaticRecord(entry);
|
|
719
|
+
if (!record) return;
|
|
720
|
+
const nodeId = `node:${nextNode++}`;
|
|
721
|
+
const nested = asStaticRecord(record.pipeline);
|
|
722
|
+
const node: StoredStaticPipelineV2Node = {
|
|
723
|
+
id: nodeId,
|
|
724
|
+
pipelineId,
|
|
725
|
+
...(parentNodeId ? { parentNodeId } : {}),
|
|
726
|
+
...(branchId ? { branchId } : {}),
|
|
727
|
+
container,
|
|
728
|
+
index,
|
|
729
|
+
value: storageSafeNodeValue(record),
|
|
730
|
+
};
|
|
731
|
+
result.nodes.push(node);
|
|
732
|
+
|
|
733
|
+
if (nested) {
|
|
734
|
+
node.nestedPipelineId = appendPipeline(
|
|
735
|
+
nested as unknown as PlayStaticPipeline,
|
|
736
|
+
nodeId,
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
if (Array.isArray(record.steps) && record.type !== 'waterfall') {
|
|
740
|
+
appendNodes(record.steps, 'steps', nodeId);
|
|
741
|
+
}
|
|
742
|
+
if (Array.isArray(record.branches)) {
|
|
743
|
+
record.branches.forEach((branch, branchIndex) => {
|
|
744
|
+
const branchRecord = asStaticRecord(branch);
|
|
745
|
+
if (!branchRecord || typeof branchRecord.label !== 'string') return;
|
|
746
|
+
const id = `branch:${nextBranch++}`;
|
|
747
|
+
result.branches.push(
|
|
748
|
+
omitUndefinedProperties({
|
|
749
|
+
id,
|
|
750
|
+
nodeId,
|
|
751
|
+
index: branchIndex,
|
|
752
|
+
label: branchRecord.label,
|
|
753
|
+
condition:
|
|
754
|
+
typeof branchRecord.condition === 'string'
|
|
755
|
+
? branchRecord.condition
|
|
756
|
+
: undefined,
|
|
757
|
+
}),
|
|
758
|
+
);
|
|
759
|
+
if (Array.isArray(branchRecord.steps)) {
|
|
760
|
+
appendNodes(branchRecord.steps, 'branch_steps', nodeId, id);
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
appendNodes(current.stages ?? [], 'stages');
|
|
768
|
+
appendNodes(current.substeps ?? [], 'substeps');
|
|
769
|
+
return pipelineId;
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
result.rootPipelineId = appendPipeline(bounded);
|
|
773
|
+
return result;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
export const STATIC_PIPELINE_V2_INTEGRITY_ERROR_CODE =
|
|
777
|
+
'PLAY_STATIC_PIPELINE_V2_INVALID';
|
|
778
|
+
export const STATIC_PIPELINE_V2_INTEGRITY_USER_MESSAGE =
|
|
779
|
+
"This Play's stored graph is inconsistent. Republish the Play to repair it before running it again.";
|
|
780
|
+
|
|
781
|
+
export class StoredStaticPipelineV2IntegrityError extends Error {
|
|
782
|
+
constructor(reason: string) {
|
|
783
|
+
super(
|
|
784
|
+
`${STATIC_PIPELINE_V2_INTEGRITY_ERROR_CODE}: ${STATIC_PIPELINE_V2_INTEGRITY_USER_MESSAGE} Internal reason: ${reason}.`,
|
|
785
|
+
);
|
|
786
|
+
this.name = 'StoredStaticPipelineV2IntegrityError';
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
export function isStoredStaticPipelineV2IntegrityError(
|
|
791
|
+
error: unknown,
|
|
792
|
+
): boolean {
|
|
793
|
+
return (
|
|
794
|
+
error instanceof StoredStaticPipelineV2IntegrityError ||
|
|
795
|
+
(error instanceof Error &&
|
|
796
|
+
error.message.includes(STATIC_PIPELINE_V2_INTEGRITY_ERROR_CODE))
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function invalidStoredStaticPipelineV2(reason: string): never {
|
|
801
|
+
throw new StoredStaticPipelineV2IntegrityError(reason);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Reads a present V2 envelope back into the existing in-memory pipeline shape.
|
|
806
|
+
* Missing V2 is a supported legacy state. Present but malformed V2 is durable
|
|
807
|
+
* corruption and fails loudly rather than running the lossy V1 projection.
|
|
808
|
+
*/
|
|
809
|
+
export function hydrateStoredStaticPipelineV2(
|
|
810
|
+
value: unknown,
|
|
811
|
+
): PlayStaticPipeline | null {
|
|
812
|
+
if (value === undefined || value === null) return null;
|
|
813
|
+
const record = asStaticRecord(value);
|
|
814
|
+
if (
|
|
815
|
+
!record ||
|
|
816
|
+
record.version !== 2 ||
|
|
817
|
+
typeof record.rootPipelineId !== 'string' ||
|
|
818
|
+
!Array.isArray(record.pipelines) ||
|
|
819
|
+
!Array.isArray(record.nodes) ||
|
|
820
|
+
!Array.isArray(record.branches)
|
|
821
|
+
) {
|
|
822
|
+
return invalidStoredStaticPipelineV2('invalid envelope');
|
|
823
|
+
}
|
|
824
|
+
const pipelines = new Map<string, PlayStaticPipeline>();
|
|
825
|
+
for (const entry of record.pipelines) {
|
|
826
|
+
const pipeline = asStaticRecord(entry);
|
|
827
|
+
if (!pipeline || typeof pipeline.id !== 'string') {
|
|
828
|
+
return invalidStoredStaticPipelineV2('invalid pipeline record');
|
|
829
|
+
}
|
|
830
|
+
if (pipelines.has(pipeline.id)) {
|
|
831
|
+
return invalidStoredStaticPipelineV2(
|
|
832
|
+
`duplicate pipeline id ${pipeline.id}`,
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
const pipelineValue = asStaticRecord(pipeline.value);
|
|
836
|
+
if (
|
|
837
|
+
!pipelineValue ||
|
|
838
|
+
!Array.isArray(pipelineValue.fields) ||
|
|
839
|
+
pipelineValue.fields.some((field) => typeof field !== 'string')
|
|
840
|
+
) {
|
|
841
|
+
return invalidStoredStaticPipelineV2(
|
|
842
|
+
`invalid pipeline value for ${pipeline.id}`,
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
pipelines.set(pipeline.id, {
|
|
846
|
+
...(pipelineValue as unknown as PlayStaticPipeline),
|
|
847
|
+
stages: [],
|
|
848
|
+
substeps: [],
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
const root = pipelines.get(record.rootPipelineId);
|
|
852
|
+
if (!root) {
|
|
853
|
+
return invalidStoredStaticPipelineV2(
|
|
854
|
+
`missing root pipeline ${record.rootPipelineId}`,
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
const nodes = new Map<string, Record<string, unknown>>();
|
|
859
|
+
const nodeRecords = record.nodes.map((entry, position) => {
|
|
860
|
+
const node = asStaticRecord(entry);
|
|
861
|
+
if (!node) {
|
|
862
|
+
return invalidStoredStaticPipelineV2(
|
|
863
|
+
`invalid node record at position ${position}`,
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
return node;
|
|
867
|
+
});
|
|
868
|
+
nodeRecords.sort((a, b) => Number(a.index) - Number(b.index));
|
|
869
|
+
for (const node of nodeRecords) {
|
|
870
|
+
if (
|
|
871
|
+
typeof node.id !== 'string' ||
|
|
872
|
+
typeof node.pipelineId !== 'string' ||
|
|
873
|
+
typeof node.container !== 'string' ||
|
|
874
|
+
typeof node.index !== 'number' ||
|
|
875
|
+
!Number.isSafeInteger(node.index) ||
|
|
876
|
+
node.index < 0
|
|
877
|
+
)
|
|
878
|
+
return invalidStoredStaticPipelineV2('invalid node identity');
|
|
879
|
+
if (nodes.has(node.id)) {
|
|
880
|
+
return invalidStoredStaticPipelineV2(`duplicate node id ${node.id}`);
|
|
881
|
+
}
|
|
882
|
+
const nodeValue = asStaticRecord(node.value);
|
|
883
|
+
if (!nodeValue || typeof nodeValue.type !== 'string') {
|
|
884
|
+
return invalidStoredStaticPipelineV2(`invalid node value for ${node.id}`);
|
|
885
|
+
}
|
|
886
|
+
const hydratedNode = { ...nodeValue };
|
|
887
|
+
if (
|
|
888
|
+
hydratedNode.type === 'dataset' ||
|
|
889
|
+
hydratedNode.type === 'step_suite' ||
|
|
890
|
+
hydratedNode.type === 'control_flow'
|
|
891
|
+
) {
|
|
892
|
+
hydratedNode.steps = [];
|
|
893
|
+
}
|
|
894
|
+
nodes.set(node.id, hydratedNode);
|
|
895
|
+
}
|
|
896
|
+
const branches = new Map<string, Record<string, unknown>>();
|
|
897
|
+
for (const entry of record.branches) {
|
|
898
|
+
const branch = asStaticRecord(entry);
|
|
899
|
+
if (
|
|
900
|
+
!branch ||
|
|
901
|
+
typeof branch.id !== 'string' ||
|
|
902
|
+
typeof branch.nodeId !== 'string' ||
|
|
903
|
+
typeof branch.label !== 'string' ||
|
|
904
|
+
typeof branch.index !== 'number' ||
|
|
905
|
+
!Number.isSafeInteger(branch.index) ||
|
|
906
|
+
branch.index < 0
|
|
907
|
+
)
|
|
908
|
+
return invalidStoredStaticPipelineV2('invalid branch record');
|
|
909
|
+
if (branches.has(branch.id)) {
|
|
910
|
+
return invalidStoredStaticPipelineV2(`duplicate branch id ${branch.id}`);
|
|
911
|
+
}
|
|
912
|
+
branches.set(
|
|
913
|
+
branch.id,
|
|
914
|
+
omitUndefinedProperties({
|
|
915
|
+
label: branch.label,
|
|
916
|
+
condition:
|
|
917
|
+
typeof branch.condition === 'string' ? branch.condition : undefined,
|
|
918
|
+
steps: [] as PlayStaticSubstep[],
|
|
919
|
+
}),
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
const nestedPipelineParents = new Map<string, string>();
|
|
923
|
+
for (const node of nodeRecords) {
|
|
924
|
+
if (typeof node.nestedPipelineId !== 'string') continue;
|
|
925
|
+
const parentPipelineId = node.pipelineId as string;
|
|
926
|
+
const nestedPipelineId = node.nestedPipelineId;
|
|
927
|
+
if (!pipelines.has(nestedPipelineId)) {
|
|
928
|
+
return invalidStoredStaticPipelineV2(
|
|
929
|
+
`node ${String(node.id)} references a missing nested pipeline`,
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
if (nestedPipelineParents.has(nestedPipelineId)) {
|
|
933
|
+
return invalidStoredStaticPipelineV2(
|
|
934
|
+
`nested pipeline ${nestedPipelineId} has multiple parents`,
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
nestedPipelineParents.set(nestedPipelineId, parentPipelineId);
|
|
938
|
+
let ancestor: string | undefined = parentPipelineId;
|
|
939
|
+
while (ancestor !== undefined) {
|
|
940
|
+
if (ancestor === nestedPipelineId) {
|
|
941
|
+
return invalidStoredStaticPipelineV2(
|
|
942
|
+
`nested pipeline ${nestedPipelineId} forms a cycle`,
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
ancestor = nestedPipelineParents.get(ancestor);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
for (const node of nodeRecords) {
|
|
949
|
+
const nodeValue = nodes.get(node.id as string);
|
|
950
|
+
const pipeline = pipelines.get(node.pipelineId as string);
|
|
951
|
+
if (!nodeValue || !pipeline) {
|
|
952
|
+
return invalidStoredStaticPipelineV2(
|
|
953
|
+
`node ${String(node.id)} references a missing pipeline`,
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
const container = node.container as StoredStaticPipelineV2Node['container'];
|
|
957
|
+
if (container === 'stages' || container === 'substeps') {
|
|
958
|
+
(pipeline[container] as PlayStaticSubstep[]).push(
|
|
959
|
+
nodeValue as PlayStaticSubstep,
|
|
960
|
+
);
|
|
961
|
+
} else if (container === 'steps') {
|
|
962
|
+
const parent = nodes.get(node.parentNodeId as string);
|
|
963
|
+
if (!parent) {
|
|
964
|
+
return invalidStoredStaticPipelineV2(
|
|
965
|
+
`node ${String(node.id)} references a missing parent`,
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
const steps = (parent.steps ??= []) as PlayStaticSubstep[];
|
|
969
|
+
steps.push(nodeValue as PlayStaticSubstep);
|
|
970
|
+
} else if (container === 'branch_steps') {
|
|
971
|
+
const branch = branches.get(node.branchId as string);
|
|
972
|
+
if (!branch) {
|
|
973
|
+
return invalidStoredStaticPipelineV2(
|
|
974
|
+
`node ${String(node.id)} references a missing branch`,
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
(branch.steps as PlayStaticSubstep[]).push(
|
|
978
|
+
nodeValue as PlayStaticSubstep,
|
|
979
|
+
);
|
|
980
|
+
} else {
|
|
981
|
+
return invalidStoredStaticPipelineV2(
|
|
982
|
+
`node ${String(node.id)} has an invalid container`,
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
if (typeof node.nestedPipelineId === 'string') {
|
|
986
|
+
const nested = pipelines.get(node.nestedPipelineId)!;
|
|
987
|
+
nodeValue.pipeline = nested;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
for (const branchRecord of record.branches) {
|
|
991
|
+
const branch = asStaticRecord(branchRecord);
|
|
992
|
+
if (!branch) continue;
|
|
993
|
+
const parent = nodes.get(branch.nodeId as string);
|
|
994
|
+
const hydrated = branches.get(branch.id as string);
|
|
995
|
+
if (!parent || !hydrated) {
|
|
996
|
+
return invalidStoredStaticPipelineV2(
|
|
997
|
+
`branch ${String(branch.id)} references a missing node`,
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
const parentBranches = (parent.branches ??= []) as Record<
|
|
1001
|
+
string,
|
|
1002
|
+
unknown
|
|
1003
|
+
>[];
|
|
1004
|
+
parentBranches.push(hydrated);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// V2 deliberately omits the recursive `columns[].producers[].substep`
|
|
1008
|
+
// structure from node values to stay below Convex's document-depth limit.
|
|
1009
|
+
// Rebuild that derived provenance from each dataset's hydrated steps before
|
|
1010
|
+
// returning the ordinary in-memory pipeline shape.
|
|
1011
|
+
for (const pipeline of [...pipelines.values()].reverse()) {
|
|
1012
|
+
const pipelineSubsteps = pipeline.substeps ?? [];
|
|
1013
|
+
const compile = (substep: PlayStaticSubstep) =>
|
|
1014
|
+
compileStaticGraphSubstep(substep, pipelineSubsteps, []);
|
|
1015
|
+
pipeline.stages = (pipeline.stages ?? []).map(compile);
|
|
1016
|
+
pipeline.substeps = pipelineSubsteps.map(compile);
|
|
1017
|
+
}
|
|
1018
|
+
return root;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
function storageObjectDepth(value: unknown): number {
|
|
1022
|
+
if (!value || typeof value !== 'object') return 0;
|
|
1023
|
+
const children = Array.isArray(value)
|
|
1024
|
+
? value
|
|
1025
|
+
: Object.values(value as Record<string, unknown>);
|
|
1026
|
+
return (
|
|
1027
|
+
1 +
|
|
1028
|
+
children.reduce((max, child) => Math.max(max, storageObjectDepth(child)), 0)
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
/**
|
|
1033
|
+
* V1 remains byte-for-byte shape compatible whenever it is safely shallow.
|
|
1034
|
+
* Only a graph that would leave too little room for its enclosing Convex
|
|
1035
|
+
* document receives the compact compatibility projection.
|
|
1036
|
+
*/
|
|
1037
|
+
export function createStaticPipelineV1CompatibilityProjection(
|
|
1038
|
+
pipeline: PlayStaticPipeline | null | undefined,
|
|
1039
|
+
): PlayStaticPipeline | null | undefined {
|
|
1040
|
+
if (!pipeline) return pipeline;
|
|
1041
|
+
const stored = truncateStaticPipelineForStorage(pipeline);
|
|
1042
|
+
if (!stored) return stored;
|
|
1043
|
+
// The containing `playRevisions` / `playRuns` document adds several object
|
|
1044
|
+
// levels. Keep the V1 graph below this conservative bound; V2 has the full
|
|
1045
|
+
// graph for all current readers.
|
|
1046
|
+
if (storageObjectDepth(stored) <= 11) return stored;
|
|
1047
|
+
const shallow = (substeps: PlayStaticSubstep[] | undefined) =>
|
|
1048
|
+
(substeps ?? []).map((substep) => {
|
|
1049
|
+
const value = storageSafeNodeValue(
|
|
1050
|
+
substep as unknown as Record<string, unknown>,
|
|
1051
|
+
);
|
|
1052
|
+
if (
|
|
1053
|
+
substep.type === 'dataset' ||
|
|
1054
|
+
substep.type === 'step_suite' ||
|
|
1055
|
+
substep.type === 'control_flow'
|
|
1056
|
+
) {
|
|
1057
|
+
value.steps = [];
|
|
1058
|
+
}
|
|
1059
|
+
return value as PlayStaticSubstep;
|
|
1060
|
+
});
|
|
1061
|
+
return {
|
|
1062
|
+
...storageSafePipelineValue(stored),
|
|
1063
|
+
stages: shallow(stored.stages),
|
|
1064
|
+
substeps: shallow(stored.substeps),
|
|
1065
|
+
} as PlayStaticPipeline;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* Creates the two transport-safe fields persisted by Convex Play records.
|
|
1070
|
+
* Call this before crossing a Convex function boundary: the complete graph is
|
|
1071
|
+
* flat in V2, while V1 remains a deliberately bounded compatibility view.
|
|
1072
|
+
*/
|
|
1073
|
+
export function createStaticPipelineStorageFields(
|
|
1074
|
+
pipeline: PlayStaticPipeline | null | undefined,
|
|
1075
|
+
): {
|
|
1076
|
+
staticPipeline?: PlayStaticPipeline | null;
|
|
1077
|
+
staticPipelineV2?: StoredStaticPipelineV2 | null;
|
|
1078
|
+
} {
|
|
1079
|
+
if (pipeline === undefined) return {};
|
|
1080
|
+
return {
|
|
1081
|
+
staticPipeline: createStaticPipelineV1CompatibilityProjection(pipeline),
|
|
1082
|
+
staticPipelineV2: createStoredStaticPipelineV2(pipeline),
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
/** Prefers complete V2. Falls back only when a legacy row has no V2 value. */
|
|
1087
|
+
export function readStoredStaticPipeline(input: {
|
|
1088
|
+
staticPipeline?: unknown;
|
|
1089
|
+
staticPipelineV2?: unknown;
|
|
1090
|
+
}): PlayStaticPipeline | null {
|
|
1091
|
+
if (input.staticPipelineV2 !== undefined && input.staticPipelineV2 !== null) {
|
|
1092
|
+
return hydrateStoredStaticPipelineV2(input.staticPipelineV2);
|
|
1093
|
+
}
|
|
1094
|
+
return (
|
|
1095
|
+
(input.staticPipeline as PlayStaticPipeline | null | undefined) ?? null
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
549
1099
|
export interface PlayStaticSourceRange {
|
|
550
1100
|
sourcePath?: string;
|
|
551
1101
|
startLine: number;
|
|
@@ -923,15 +1473,20 @@ function columnProducerFromSubstep(
|
|
|
923
1473
|
substep: PlayStaticSubstep,
|
|
924
1474
|
field: string,
|
|
925
1475
|
): PlayStaticColumnProducer {
|
|
926
|
-
const
|
|
927
|
-
substep.type === '
|
|
928
|
-
? substep.steps
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1476
|
+
const nestedProducerSubsteps =
|
|
1477
|
+
substep.type === 'control_flow' && substep.steps.length === 0
|
|
1478
|
+
? (substep.branches ?? []).flatMap((branch) => branch.steps)
|
|
1479
|
+
: substep.type === 'step_suite' || substep.type === 'control_flow'
|
|
1480
|
+
? substep.steps
|
|
1481
|
+
: null;
|
|
1482
|
+
const steps = nestedProducerSubsteps
|
|
1483
|
+
? nestedProducerSubsteps
|
|
1484
|
+
.map((step) => {
|
|
1485
|
+
const stepField = fieldForColumnProducer(step) ?? field;
|
|
1486
|
+
return columnProducerFromSubstep(step, stepField);
|
|
1487
|
+
})
|
|
1488
|
+
.filter((producer) => producer.field.trim())
|
|
1489
|
+
: undefined;
|
|
935
1490
|
const kind: PlayStaticColumnProducerKind =
|
|
936
1491
|
substep.type === 'tool'
|
|
937
1492
|
? 'tool'
|
package/dist/cli/index.js
CHANGED
|
@@ -1044,7 +1044,7 @@ var SDK_RELEASE = {
|
|
|
1044
1044
|
// 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
|
|
1045
1045
|
// exposed storage-dependent synchronous access. This deliberate minor
|
|
1046
1046
|
// release keeps lazy paging semantics independent of row residency.
|
|
1047
|
-
version: "0.2.
|
|
1047
|
+
version: "0.2.20",
|
|
1048
1048
|
contracts: {
|
|
1049
1049
|
api: {
|
|
1050
1050
|
name: "sdk-http-api",
|
|
@@ -6479,6 +6479,10 @@ function collectLocalEnvInfo() {
|
|
|
6479
6479
|
function readCsvRows(csvPath) {
|
|
6480
6480
|
const raw = (0, import_node_fs4.readFileSync)((0, import_node_path4.resolve)(csvPath), "utf-8");
|
|
6481
6481
|
return (0, import_sync.parse)(raw, {
|
|
6482
|
+
// `csv-parse` otherwise treats a BOM before an opening quote as a field
|
|
6483
|
+
// value, then rejects the quote as invalid. A UTF-8 BOM is a valid file
|
|
6484
|
+
// prefix and must not become part of the first column name either.
|
|
6485
|
+
bom: true,
|
|
6482
6486
|
columns: true,
|
|
6483
6487
|
skip_empty_lines: true
|
|
6484
6488
|
});
|
|
@@ -15923,27 +15927,6 @@ var SECRET_ENV_PATTERN = /\bprocess(?:\.env|\[['"]env['"]\])(?:\.|\[['"])([A-Z0-
|
|
|
15923
15927
|
var PRIVATE_KEY_PATTERN = /-----BEGIN (?:RSA |EC |OPENSSH |PGP )?PRIVATE KEY-----/;
|
|
15924
15928
|
var BEARER_LITERAL_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/i;
|
|
15925
15929
|
var ASSIGNMENT_SECRET_LITERAL_PATTERN = /\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*['"][^'"]{12,}['"]/i;
|
|
15926
|
-
var HIGH_ENTROPY_LITERAL_PATTERN = /['"]([A-Za-z0-9+/=_-]{32,})['"]/g;
|
|
15927
|
-
var UUID_IDENTIFIER_PATTERN = /^((?:[A-Za-z0-9]+[-_])*)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
|
|
15928
|
-
var BOOTSTRAP_RESOURCE_IDENTIFIER_PATTERN = /^bootstrap-[0-9a-f]{32}(?:\/[a-z0-9][a-z0-9_-]{0,127})?$/i;
|
|
15929
|
-
var SECRET_LABEL_PATTERN = /(?:^|[-_])(?:api|auth|access|secret|token|key|password|credential|bearer|sk|pk|live)(?:[-_]|$)/i;
|
|
15930
|
-
function shannonEntropy(value) {
|
|
15931
|
-
const counts = /* @__PURE__ */ new Map();
|
|
15932
|
-
for (const char of value) counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
15933
|
-
return [...counts.values()].reduce((entropy, count) => {
|
|
15934
|
-
const p = count / value.length;
|
|
15935
|
-
return entropy - p * Math.log2(p);
|
|
15936
|
-
}, 0);
|
|
15937
|
-
}
|
|
15938
|
-
function isNonSecretUuidIdentifier(value) {
|
|
15939
|
-
const match = UUID_IDENTIFIER_PATTERN.exec(value);
|
|
15940
|
-
if (!match) return false;
|
|
15941
|
-
const label = match[1] ?? "";
|
|
15942
|
-
return !SECRET_LABEL_PATTERN.test(label);
|
|
15943
|
-
}
|
|
15944
|
-
function isNonSecretBootstrapResourceIdentifier(value) {
|
|
15945
|
-
return BOOTSTRAP_RESOURCE_IDENTIFIER_PATTERN.test(value);
|
|
15946
|
-
}
|
|
15947
15930
|
function collectInlineSecretFindings(sourceCode) {
|
|
15948
15931
|
const findings = [];
|
|
15949
15932
|
for (const match of sourceCode.matchAll(SECRET_ENV_PATTERN)) {
|
|
@@ -15955,15 +15938,6 @@ function collectInlineSecretFindings(sourceCode) {
|
|
|
15955
15938
|
if (ASSIGNMENT_SECRET_LITERAL_PATTERN.test(sourceCode)) {
|
|
15956
15939
|
findings.push("secret-looking assignment literal");
|
|
15957
15940
|
}
|
|
15958
|
-
for (const match of sourceCode.matchAll(HIGH_ENTROPY_LITERAL_PATTERN)) {
|
|
15959
|
-
const literal = match[1] ?? "";
|
|
15960
|
-
if (isNonSecretUuidIdentifier(literal)) continue;
|
|
15961
|
-
if (isNonSecretBootstrapResourceIdentifier(literal)) continue;
|
|
15962
|
-
if (literal.length >= 40 && shannonEntropy(literal) >= 4.2) {
|
|
15963
|
-
findings.push("high-entropy string literal");
|
|
15964
|
-
break;
|
|
15965
|
-
}
|
|
15966
|
-
}
|
|
15967
15941
|
return [...new Set(findings)];
|
|
15968
15942
|
}
|
|
15969
15943
|
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1030,7 +1030,7 @@ var SDK_RELEASE = {
|
|
|
1030
1030
|
// 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
|
|
1031
1031
|
// exposed storage-dependent synchronous access. This deliberate minor
|
|
1032
1032
|
// release keeps lazy paging semantics independent of row residency.
|
|
1033
|
-
version: "0.2.
|
|
1033
|
+
version: "0.2.20",
|
|
1034
1034
|
contracts: {
|
|
1035
1035
|
api: {
|
|
1036
1036
|
name: "sdk-http-api",
|
|
@@ -6477,6 +6477,10 @@ function collectLocalEnvInfo() {
|
|
|
6477
6477
|
function readCsvRows(csvPath) {
|
|
6478
6478
|
const raw = readFileSync4(resolve2(csvPath), "utf-8");
|
|
6479
6479
|
return parse(raw, {
|
|
6480
|
+
// `csv-parse` otherwise treats a BOM before an opening quote as a field
|
|
6481
|
+
// value, then rejects the quote as invalid. A UTF-8 BOM is a valid file
|
|
6482
|
+
// prefix and must not become part of the first column name either.
|
|
6483
|
+
bom: true,
|
|
6480
6484
|
columns: true,
|
|
6481
6485
|
skip_empty_lines: true
|
|
6482
6486
|
});
|
|
@@ -15960,27 +15964,6 @@ var SECRET_ENV_PATTERN = /\bprocess(?:\.env|\[['"]env['"]\])(?:\.|\[['"])([A-Z0-
|
|
|
15960
15964
|
var PRIVATE_KEY_PATTERN = /-----BEGIN (?:RSA |EC |OPENSSH |PGP )?PRIVATE KEY-----/;
|
|
15961
15965
|
var BEARER_LITERAL_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/i;
|
|
15962
15966
|
var ASSIGNMENT_SECRET_LITERAL_PATTERN = /\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*['"][^'"]{12,}['"]/i;
|
|
15963
|
-
var HIGH_ENTROPY_LITERAL_PATTERN = /['"]([A-Za-z0-9+/=_-]{32,})['"]/g;
|
|
15964
|
-
var UUID_IDENTIFIER_PATTERN = /^((?:[A-Za-z0-9]+[-_])*)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
|
|
15965
|
-
var BOOTSTRAP_RESOURCE_IDENTIFIER_PATTERN = /^bootstrap-[0-9a-f]{32}(?:\/[a-z0-9][a-z0-9_-]{0,127})?$/i;
|
|
15966
|
-
var SECRET_LABEL_PATTERN = /(?:^|[-_])(?:api|auth|access|secret|token|key|password|credential|bearer|sk|pk|live)(?:[-_]|$)/i;
|
|
15967
|
-
function shannonEntropy(value) {
|
|
15968
|
-
const counts = /* @__PURE__ */ new Map();
|
|
15969
|
-
for (const char of value) counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
15970
|
-
return [...counts.values()].reduce((entropy, count) => {
|
|
15971
|
-
const p = count / value.length;
|
|
15972
|
-
return entropy - p * Math.log2(p);
|
|
15973
|
-
}, 0);
|
|
15974
|
-
}
|
|
15975
|
-
function isNonSecretUuidIdentifier(value) {
|
|
15976
|
-
const match = UUID_IDENTIFIER_PATTERN.exec(value);
|
|
15977
|
-
if (!match) return false;
|
|
15978
|
-
const label = match[1] ?? "";
|
|
15979
|
-
return !SECRET_LABEL_PATTERN.test(label);
|
|
15980
|
-
}
|
|
15981
|
-
function isNonSecretBootstrapResourceIdentifier(value) {
|
|
15982
|
-
return BOOTSTRAP_RESOURCE_IDENTIFIER_PATTERN.test(value);
|
|
15983
|
-
}
|
|
15984
15967
|
function collectInlineSecretFindings(sourceCode) {
|
|
15985
15968
|
const findings = [];
|
|
15986
15969
|
for (const match of sourceCode.matchAll(SECRET_ENV_PATTERN)) {
|
|
@@ -15992,15 +15975,6 @@ function collectInlineSecretFindings(sourceCode) {
|
|
|
15992
15975
|
if (ASSIGNMENT_SECRET_LITERAL_PATTERN.test(sourceCode)) {
|
|
15993
15976
|
findings.push("secret-looking assignment literal");
|
|
15994
15977
|
}
|
|
15995
|
-
for (const match of sourceCode.matchAll(HIGH_ENTROPY_LITERAL_PATTERN)) {
|
|
15996
|
-
const literal = match[1] ?? "";
|
|
15997
|
-
if (isNonSecretUuidIdentifier(literal)) continue;
|
|
15998
|
-
if (isNonSecretBootstrapResourceIdentifier(literal)) continue;
|
|
15999
|
-
if (literal.length >= 40 && shannonEntropy(literal) >= 4.2) {
|
|
16000
|
-
findings.push("high-entropy string literal");
|
|
16001
|
-
break;
|
|
16002
|
-
}
|
|
16003
|
-
}
|
|
16004
15978
|
return [...new Set(findings)];
|
|
16005
15979
|
}
|
|
16006
15980
|
|
package/dist/index.js
CHANGED
|
@@ -763,7 +763,7 @@ var SDK_RELEASE = {
|
|
|
763
763
|
// 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
|
|
764
764
|
// exposed storage-dependent synchronous access. This deliberate minor
|
|
765
765
|
// release keeps lazy paging semantics independent of row residency.
|
|
766
|
-
version: "0.2.
|
|
766
|
+
version: "0.2.20",
|
|
767
767
|
contracts: {
|
|
768
768
|
api: {
|
|
769
769
|
name: "sdk-http-api",
|
package/dist/index.mjs
CHANGED
|
@@ -689,7 +689,7 @@ var SDK_RELEASE = {
|
|
|
689
689
|
// 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
|
|
690
690
|
// exposed storage-dependent synchronous access. This deliberate minor
|
|
691
691
|
// release keeps lazy paging semantics independent of row residency.
|
|
692
|
-
version: "0.2.
|
|
692
|
+
version: "0.2.20",
|
|
693
693
|
contracts: {
|
|
694
694
|
api: {
|
|
695
695
|
name: "sdk-http-api",
|
|
@@ -3921,27 +3921,6 @@ var SECRET_ENV_PATTERN = /\bprocess(?:\.env|\[['"]env['"]\])(?:\.|\[['"])([A-Z0-
|
|
|
3921
3921
|
var PRIVATE_KEY_PATTERN = /-----BEGIN (?:RSA |EC |OPENSSH |PGP )?PRIVATE KEY-----/;
|
|
3922
3922
|
var BEARER_LITERAL_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/i;
|
|
3923
3923
|
var ASSIGNMENT_SECRET_LITERAL_PATTERN = /\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*['"][^'"]{12,}['"]/i;
|
|
3924
|
-
var HIGH_ENTROPY_LITERAL_PATTERN = /['"]([A-Za-z0-9+/=_-]{32,})['"]/g;
|
|
3925
|
-
var UUID_IDENTIFIER_PATTERN = /^((?:[A-Za-z0-9]+[-_])*)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
|
|
3926
|
-
var BOOTSTRAP_RESOURCE_IDENTIFIER_PATTERN = /^bootstrap-[0-9a-f]{32}(?:\/[a-z0-9][a-z0-9_-]{0,127})?$/i;
|
|
3927
|
-
var SECRET_LABEL_PATTERN = /(?:^|[-_])(?:api|auth|access|secret|token|key|password|credential|bearer|sk|pk|live)(?:[-_]|$)/i;
|
|
3928
|
-
function shannonEntropy(value) {
|
|
3929
|
-
const counts = /* @__PURE__ */ new Map();
|
|
3930
|
-
for (const char of value) counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
3931
|
-
return [...counts.values()].reduce((entropy, count) => {
|
|
3932
|
-
const p = count / value.length;
|
|
3933
|
-
return entropy - p * Math.log2(p);
|
|
3934
|
-
}, 0);
|
|
3935
|
-
}
|
|
3936
|
-
function isNonSecretUuidIdentifier(value) {
|
|
3937
|
-
const match = UUID_IDENTIFIER_PATTERN.exec(value);
|
|
3938
|
-
if (!match) return false;
|
|
3939
|
-
const label = match[1] ?? "";
|
|
3940
|
-
return !SECRET_LABEL_PATTERN.test(label);
|
|
3941
|
-
}
|
|
3942
|
-
function isNonSecretBootstrapResourceIdentifier(value) {
|
|
3943
|
-
return BOOTSTRAP_RESOURCE_IDENTIFIER_PATTERN.test(value);
|
|
3944
|
-
}
|
|
3945
3924
|
function collectInlineSecretFindings(sourceCode) {
|
|
3946
3925
|
const findings = [];
|
|
3947
3926
|
for (const match of sourceCode.matchAll(SECRET_ENV_PATTERN)) {
|
|
@@ -3953,15 +3932,6 @@ function collectInlineSecretFindings(sourceCode) {
|
|
|
3953
3932
|
if (ASSIGNMENT_SECRET_LITERAL_PATTERN.test(sourceCode)) {
|
|
3954
3933
|
findings.push("secret-looking assignment literal");
|
|
3955
3934
|
}
|
|
3956
|
-
for (const match of sourceCode.matchAll(HIGH_ENTROPY_LITERAL_PATTERN)) {
|
|
3957
|
-
const literal = match[1] ?? "";
|
|
3958
|
-
if (isNonSecretUuidIdentifier(literal)) continue;
|
|
3959
|
-
if (isNonSecretBootstrapResourceIdentifier(literal)) continue;
|
|
3960
|
-
if (literal.length >= 40 && shannonEntropy(literal) >= 4.2) {
|
|
3961
|
-
findings.push("high-entropy string literal");
|
|
3962
|
-
break;
|
|
3963
|
-
}
|
|
3964
|
-
}
|
|
3965
3935
|
return [...new Set(findings)];
|
|
3966
3936
|
}
|
|
3967
3937
|
function validatePlaySourceHasNoInlineSecrets(input) {
|