@dataparade/cli 0.0.2 → 0.0.3
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/README.md +1 -0
- package/dist/src/core/pipeline/graph-mapping.js +5 -0
- package/dist/src/core/pipeline/infer-data-flow-protocol.d.ts +7 -0
- package/dist/src/core/pipeline/infer-data-flow-protocol.js +39 -0
- package/dist/tests/unit/analyzers/typescript/property-detection.spec.js +22 -0
- package/dist/tests/unit/core/graph-mapping.spec.js +52 -0
- package/dist/tests/unit/core/infer-data-flow-protocol.spec.d.ts +1 -0
- package/dist/tests/unit/core/infer-data-flow-protocol.spec.js +37 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,6 +112,7 @@ The CLI emits **all** Engineering, Privacy, and Security property keys (from the
|
|
|
112
112
|
- Database: `connection_encryption`, `backup_frequency`, `audit_logging_enabled`, etc. when patterns match.
|
|
113
113
|
- Auth: `mfa_required`, `authentication_method`, `sso_integration` from auth middleware patterns.
|
|
114
114
|
- Routes: `request_validation`, `api_type`, `https_enforced` for route patterns.
|
|
115
|
+
- Data flow edges (graph mapping): `engineering.protocol` is set to `rest` or `graphql` for `api_call` flows when the endpoint, method, or source code indicates HTTP REST vs GraphQL (see `infer-data-flow-protocol.ts`). Pattern rules in `property.patterns.yaml` also set `api_type` to `graphql` for `/graphql` URLs and paths.
|
|
115
116
|
|
|
116
117
|
**Terraform / IaC (`.tf` / `.tfvars`):**
|
|
117
118
|
|
|
@@ -10,6 +10,7 @@ const minimal_terraform_layout_1 = require("./diagram-layout/minimal-terraform-l
|
|
|
10
10
|
const section_helpers_1 = require("./diagram-layout/section-helpers");
|
|
11
11
|
const terraform_lane_layout_1 = require("./diagram-layout/terraform-lane-layout");
|
|
12
12
|
const terraform_minimal_services_1 = require("./terraform-minimal-services");
|
|
13
|
+
const infer_data_flow_protocol_1 = require("./infer-data-flow-protocol");
|
|
13
14
|
function stripCodeFromSourceLocation(loc) {
|
|
14
15
|
if (!loc)
|
|
15
16
|
return undefined;
|
|
@@ -157,6 +158,10 @@ function mapDataFlowToEdge(flow, componentsById) {
|
|
|
157
158
|
if (flow.actions && flow.actions.length > 0) {
|
|
158
159
|
engineering.actions = flow.actions;
|
|
159
160
|
}
|
|
161
|
+
const protocol = (0, infer_data_flow_protocol_1.inferDataFlowProtocol)(flow);
|
|
162
|
+
if (protocol) {
|
|
163
|
+
engineering.protocol = protocol;
|
|
164
|
+
}
|
|
160
165
|
const privacy = {
|
|
161
166
|
dataCategories: flow.dataCategories ?? [],
|
|
162
167
|
dataSubjectCategories: flow.dataSubjectCategories,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DetectedDataFlow } from "../types/data-flow";
|
|
2
|
+
export type InferredDataFlowProtocol = "rest" | "graphql";
|
|
3
|
+
/**
|
|
4
|
+
* Infer engineering.protocol for a scanned data flow (rest | graphql).
|
|
5
|
+
* Returns undefined when the flow type is not HTTP/API-shaped or there is no signal.
|
|
6
|
+
*/
|
|
7
|
+
export declare function inferDataFlowProtocol(flow: DetectedDataFlow): InferredDataFlowProtocol | undefined;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.inferDataFlowProtocol = inferDataFlowProtocol;
|
|
4
|
+
const GRAPHQL_SIGNAL = /\bgraphql\b|\/graphql\b/i;
|
|
5
|
+
function textSuggestsGraphql(text) {
|
|
6
|
+
return GRAPHQL_SIGNAL.test(text);
|
|
7
|
+
}
|
|
8
|
+
function flowCodeSuggestsGraphql(flow) {
|
|
9
|
+
const locations = [
|
|
10
|
+
flow.sourceLocation,
|
|
11
|
+
...(flow.sourceLocations ?? []),
|
|
12
|
+
].filter((loc) => loc != null);
|
|
13
|
+
for (const loc of locations) {
|
|
14
|
+
if (typeof loc.code === "string" && textSuggestsGraphql(loc.code)) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Infer engineering.protocol for a scanned data flow (rest | graphql).
|
|
22
|
+
* Returns undefined when the flow type is not HTTP/API-shaped or there is no signal.
|
|
23
|
+
*/
|
|
24
|
+
function inferDataFlowProtocol(flow) {
|
|
25
|
+
const endpoint = flow.endpoint?.trim() ?? "";
|
|
26
|
+
if (endpoint && textSuggestsGraphql(endpoint)) {
|
|
27
|
+
return "graphql";
|
|
28
|
+
}
|
|
29
|
+
if (flowCodeSuggestsGraphql(flow)) {
|
|
30
|
+
return "graphql";
|
|
31
|
+
}
|
|
32
|
+
if (flow.type !== "api_call") {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
if (endpoint || flow.method?.trim()) {
|
|
36
|
+
return "rest";
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
@@ -122,6 +122,18 @@ describe("property-detection", () => {
|
|
|
122
122
|
expect(out.integration_method).toBe("api");
|
|
123
123
|
expect(out.authentication_method).toBe("api_key");
|
|
124
124
|
});
|
|
125
|
+
it("sets api_type graphql when url path is GraphQL", () => {
|
|
126
|
+
const finding = makeFinding({
|
|
127
|
+
pattern: "external_api_call",
|
|
128
|
+
name: "Graph API",
|
|
129
|
+
properties: {
|
|
130
|
+
serviceName: "internal",
|
|
131
|
+
url: "https://api.example.com/graphql",
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
const out = (0, property_inference_1.getPropertiesFromFinding)(finding);
|
|
135
|
+
expect(out.api_type).toBe("graphql");
|
|
136
|
+
});
|
|
125
137
|
});
|
|
126
138
|
describe("express_route", () => {
|
|
127
139
|
it("sets request_validation and connection_encryption", () => {
|
|
@@ -133,6 +145,16 @@ describe("property-detection", () => {
|
|
|
133
145
|
const out = (0, property_inference_1.getPropertiesFromFinding)(finding);
|
|
134
146
|
expect(out.request_validation).toBe(true);
|
|
135
147
|
expect(out.connection_encryption).toBe(true);
|
|
148
|
+
expect(out.api_type).toBe("rest");
|
|
149
|
+
});
|
|
150
|
+
it("sets api_type graphql for GraphQL route path", () => {
|
|
151
|
+
const finding = makeFinding({
|
|
152
|
+
pattern: "express_route",
|
|
153
|
+
name: "POST /graphql",
|
|
154
|
+
properties: { httpMethods: ["POST"], path: "/graphql" },
|
|
155
|
+
});
|
|
156
|
+
const out = (0, property_inference_1.getPropertiesFromFinding)(finding);
|
|
157
|
+
expect(out.api_type).toBe("graphql");
|
|
136
158
|
});
|
|
137
159
|
});
|
|
138
160
|
});
|
|
@@ -106,6 +106,7 @@ describe("core/pipeline/graph-mapping - DP-P0-CLI-402", () => {
|
|
|
106
106
|
return;
|
|
107
107
|
const properties = edge.data.properties;
|
|
108
108
|
expect(properties.engineering.transferType).toBe("api_call");
|
|
109
|
+
expect(properties.engineering.protocol).toBe("rest");
|
|
109
110
|
expect(properties.engineering.name).toContain("Test Application");
|
|
110
111
|
expect(properties.engineering.actions).toEqual(["read"]);
|
|
111
112
|
expect(properties.privacy.dataCategories).toEqual([
|
|
@@ -122,6 +123,57 @@ describe("core/pipeline/graph-mapping - DP-P0-CLI-402", () => {
|
|
|
122
123
|
expect(properties.targetScopeConfidence).toBe("high");
|
|
123
124
|
expect(properties.targetScopeReason).toBe("same-section-id");
|
|
124
125
|
});
|
|
126
|
+
it("sets engineering.protocol to graphql when flow endpoint is a GraphQL URL", () => {
|
|
127
|
+
const scanResult = {
|
|
128
|
+
components: [
|
|
129
|
+
{
|
|
130
|
+
id: "component-1",
|
|
131
|
+
name: "API Service",
|
|
132
|
+
type: "asset",
|
|
133
|
+
subType: "api",
|
|
134
|
+
confidence: 0.9,
|
|
135
|
+
detectedFrom: [{ pattern: "express_route" }],
|
|
136
|
+
sourceLocations: [
|
|
137
|
+
{ filePath: "src/api.ts", startLine: 1, endLine: 10 },
|
|
138
|
+
],
|
|
139
|
+
properties: {},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: "component-2",
|
|
143
|
+
name: "External API",
|
|
144
|
+
type: "third_party",
|
|
145
|
+
subType: "saas_service",
|
|
146
|
+
confidence: 0.9,
|
|
147
|
+
detectedFrom: [{ pattern: "external_api_call" }],
|
|
148
|
+
sourceLocations: [
|
|
149
|
+
{ filePath: "src/api.ts", startLine: 20, endLine: 30 },
|
|
150
|
+
],
|
|
151
|
+
properties: {},
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
dataFlows: [
|
|
155
|
+
{
|
|
156
|
+
id: "flow-gql",
|
|
157
|
+
sourceComponentId: "component-1",
|
|
158
|
+
targetComponentId: "component-2",
|
|
159
|
+
type: "api_call",
|
|
160
|
+
confidence: 0.9,
|
|
161
|
+
endpoint: "https://api.example.com/graphql",
|
|
162
|
+
method: "POST",
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
filesScanned: 1,
|
|
166
|
+
filesSkipped: 0,
|
|
167
|
+
totalLines: 50,
|
|
168
|
+
scanDurationMs: 5,
|
|
169
|
+
warnings: [],
|
|
170
|
+
errors: [],
|
|
171
|
+
};
|
|
172
|
+
const graph = (0, graph_mapping_1.buildDiagramGraphFromScanResult)(scanResult);
|
|
173
|
+
const properties = graph.edges[0]?.data
|
|
174
|
+
?.properties;
|
|
175
|
+
expect(properties?.engineering?.protocol).toBe("graphql");
|
|
176
|
+
});
|
|
125
177
|
it("places managed provider service nodes after provider in layout", () => {
|
|
126
178
|
const scanResult = {
|
|
127
179
|
components: [
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const infer_data_flow_protocol_1 = require("../../../src/core/pipeline/infer-data-flow-protocol");
|
|
4
|
+
function baseFlow(overrides = {}) {
|
|
5
|
+
return {
|
|
6
|
+
id: "flow-1",
|
|
7
|
+
sourceComponentId: "a",
|
|
8
|
+
targetComponentId: "b",
|
|
9
|
+
type: "api_call",
|
|
10
|
+
confidence: 0.9,
|
|
11
|
+
...overrides,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
describe("inferDataFlowProtocol", () => {
|
|
15
|
+
it("returns graphql for /graphql endpoint", () => {
|
|
16
|
+
expect((0, infer_data_flow_protocol_1.inferDataFlowProtocol)(baseFlow({ endpoint: "https://api.example.com/graphql" }))).toBe("graphql");
|
|
17
|
+
});
|
|
18
|
+
it("returns graphql when source code mentions graphql", () => {
|
|
19
|
+
expect((0, infer_data_flow_protocol_1.inferDataFlowProtocol)(baseFlow({
|
|
20
|
+
sourceLocation: {
|
|
21
|
+
filePath: "src/client.ts",
|
|
22
|
+
startLine: 1,
|
|
23
|
+
endLine: 2,
|
|
24
|
+
code: "client.query({ query: GET_USERS }) // graphql",
|
|
25
|
+
},
|
|
26
|
+
}))).toBe("graphql");
|
|
27
|
+
});
|
|
28
|
+
it("returns rest for api_call with HTTP endpoint", () => {
|
|
29
|
+
expect((0, infer_data_flow_protocol_1.inferDataFlowProtocol)(baseFlow({ endpoint: "/api/users", method: "GET" }))).toBe("rest");
|
|
30
|
+
});
|
|
31
|
+
it("returns undefined for database_query", () => {
|
|
32
|
+
expect((0, infer_data_flow_protocol_1.inferDataFlowProtocol)(baseFlow({ type: "database_query", endpoint: "postgres://..." }))).toBeUndefined();
|
|
33
|
+
});
|
|
34
|
+
it("returns undefined for api_call without endpoint or method", () => {
|
|
35
|
+
expect((0, infer_data_flow_protocol_1.inferDataFlowProtocol)(baseFlow())).toBeUndefined();
|
|
36
|
+
});
|
|
37
|
+
});
|