@yiaany/ghostapi 0.1.8 → 0.2.0
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/CHANGELOG.md +21 -0
- package/CONTRIBUTING.md +132 -119
- package/LICENSE +21 -21
- package/PROVENANCE.md +13 -0
- package/README.md +78 -80
- package/SECURITY.md +56 -53
- package/dist/actions/gateway.js +320 -72
- package/dist/actions/gateway.js.map +1 -1
- package/dist/actions/index.d.ts +2 -2
- package/dist/actions/index.js +1 -1
- package/dist/actions/index.js.map +1 -1
- package/dist/agents/agentPrompt.js +7 -5
- package/dist/agents/agentPrompt.js.map +1 -1
- package/dist/ai/aiClient.js +5 -5
- package/dist/ai/aiClient.js.map +1 -1
- package/dist/ai/aiGenerator.js +66 -26
- package/dist/ai/aiGenerator.js.map +1 -1
- package/dist/ai/genericInference.js +151 -36
- package/dist/ai/genericInference.js.map +1 -1
- package/dist/ai/prompts.js +55 -49
- package/dist/ai/prompts.js.map +1 -1
- package/dist/approvals/approvalInbox.js +649 -133
- package/dist/approvals/approvalInbox.js.map +1 -1
- package/dist/approvals/index.d.ts +2 -2
- package/dist/approvals/index.js +1 -1
- package/dist/approvals/index.js.map +1 -1
- package/dist/behavior/behaviorStore.js +34 -14
- package/dist/behavior/behaviorStore.js.map +1 -1
- package/dist/cache/index.js +14 -5
- package/dist/cache/index.js.map +1 -1
- package/dist/cli/index.js +226 -93
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/openUrl.js +17 -3
- package/dist/cli/openUrl.js.map +1 -1
- package/dist/cli/parser.js +79 -18
- package/dist/cli/parser.js.map +1 -1
- package/dist/config/dataPaths.js +4 -2
- package/dist/config/dataPaths.js.map +1 -1
- package/dist/config/localConfig.js +5 -2
- package/dist/config/localConfig.js.map +1 -1
- package/dist/config/serverConfig.js +11 -5
- package/dist/config/serverConfig.js.map +1 -1
- package/dist/contracts/contract.js +311 -83
- package/dist/contracts/contract.js.map +1 -1
- package/dist/contracts/index.d.ts +2 -2
- package/dist/contracts/index.js +1 -1
- package/dist/contracts/index.js.map +1 -1
- package/dist/credentials/credentialBroker.js +391 -79
- package/dist/credentials/credentialBroker.js.map +1 -1
- package/dist/credentials/index.d.ts +2 -2
- package/dist/credentials/index.js +1 -1
- package/dist/credentials/index.js.map +1 -1
- package/dist/dashboard/app.js +823 -685
- package/dist/dashboard/dashboard.js.map +1 -1
- package/dist/dashboard/index.html +704 -267
- package/dist/dashboard/styles.css +1040 -349
- package/dist/egress/capabilities.js +55 -25
- package/dist/egress/capabilities.js.map +1 -1
- package/dist/egress/linuxBootstrap.js +47 -11
- package/dist/egress/linuxBootstrap.js.map +1 -1
- package/dist/egress/run.js +170 -31
- package/dist/egress/run.js.map +1 -1
- package/dist/errors/errorEngine.js +15 -3
- package/dist/errors/errorEngine.js.map +1 -1
- package/dist/errors/providerErrors.js.map +1 -1
- package/dist/evals/evals.js +304 -78
- package/dist/evals/evals.js.map +1 -1
- package/dist/evals/index.d.ts +2 -2
- package/dist/evals/index.js +1 -1
- package/dist/evals/index.js.map +1 -1
- package/dist/evidence/index.d.ts +2 -2
- package/dist/evidence/index.js +1 -1
- package/dist/evidence/index.js.map +1 -1
- package/dist/evidence/report.d.ts +1 -1
- package/dist/evidence/report.js +263 -79
- package/dist/evidence/report.js.map +1 -1
- package/dist/fault/faultLab.js +24 -9
- package/dist/fault/faultLab.js.map +1 -1
- package/dist/index.d.ts +42 -42
- package/dist/index.js +21 -21
- package/dist/index.js.map +1 -1
- package/dist/inventory/analysis.d.ts +1 -1
- package/dist/inventory/analysis.js +174 -50
- package/dist/inventory/analysis.js.map +1 -1
- package/dist/inventory/index.d.ts +4 -4
- package/dist/inventory/index.js +3 -3
- package/dist/inventory/index.js.map +1 -1
- package/dist/inventory/inventoryController.js +362 -115
- package/dist/inventory/inventoryController.js.map +1 -1
- package/dist/inventory/types.js +680 -140
- package/dist/inventory/types.js.map +1 -1
- package/dist/ledger/actionLedger.js +355 -78
- package/dist/ledger/actionLedger.js.map +1 -1
- package/dist/ledger/index.d.ts +2 -2
- package/dist/ledger/index.js +1 -1
- package/dist/ledger/index.js.map +1 -1
- package/dist/mcp/server.js +26 -11
- package/dist/mcp/server.js.map +1 -1
- package/dist/policy/index.d.ts +2 -2
- package/dist/policy/index.js +1 -1
- package/dist/policy/index.js.map +1 -1
- package/dist/policy/policy.js +112 -32
- package/dist/policy/policy.js.map +1 -1
- package/dist/productTelemetry/index.js +71 -20
- package/dist/productTelemetry/index.js.map +1 -1
- package/dist/providers/conformance.js +10 -5
- package/dist/providers/conformance.js.map +1 -1
- package/dist/providers/discord.js +2 -2
- package/dist/providers/generic.js +3 -3
- package/dist/providers/github.js +2 -2
- package/dist/providers/index.d.ts +3 -3
- package/dist/providers/index.js +2 -2
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/openai.js +3 -3
- package/dist/providers/packs/resendPack.js +51 -25
- package/dist/providers/packs/resendPack.js.map +1 -1
- package/dist/providers/packs/stripePack.js +590 -121
- package/dist/providers/packs/stripePack.js.map +1 -1
- package/dist/providers/registry.js +4 -4
- package/dist/providers/registry.js.map +1 -1
- package/dist/providers/runtime.js +7 -6
- package/dist/providers/runtime.js.map +1 -1
- package/dist/providers/stripeWebhook.js +39 -10
- package/dist/providers/stripeWebhook.js.map +1 -1
- package/dist/providers/twilio.js +2 -2
- package/dist/proxy/cacheKey.js +9 -2
- package/dist/proxy/cacheKey.js.map +1 -1
- package/dist/proxy/providerDetector.js +22 -9
- package/dist/proxy/providerDetector.js.map +1 -1
- package/dist/proxy/proxyHandler.js +57 -22
- package/dist/proxy/proxyHandler.js.map +1 -1
- package/dist/proxy/requestNormalizer.js +4 -2
- package/dist/proxy/requestNormalizer.js.map +1 -1
- package/dist/reliability/costGovernance.js +211 -49
- package/dist/reliability/costGovernance.js.map +1 -1
- package/dist/reliability/index.d.ts +8 -8
- package/dist/reliability/index.js +4 -4
- package/dist/reliability/index.js.map +1 -1
- package/dist/reliability/reconciliation.js +274 -65
- package/dist/reliability/reconciliation.js.map +1 -1
- package/dist/reliability/runtimeHealth.js +127 -35
- package/dist/reliability/runtimeHealth.js.map +1 -1
- package/dist/reliability/slo.js +257 -50
- package/dist/reliability/slo.js.map +1 -1
- package/dist/report/safetyReport.d.ts +6 -0
- package/dist/report/safetyReport.js +56 -12
- package/dist/report/safetyReport.js.map +1 -1
- package/dist/rules/aiRules.js +5 -5
- package/dist/rules/aiRules.js.map +1 -1
- package/dist/safety/index.d.ts +2 -2
- package/dist/safety/index.js +1 -1
- package/dist/safety/index.js.map +1 -1
- package/dist/safety/safetyController.js +675 -165
- package/dist/safety/safetyController.js.map +1 -1
- package/dist/scenarios/scenarioBundle.js +204 -71
- package/dist/scenarios/scenarioBundle.js.map +1 -1
- package/dist/scenarios/scenarioStore.js +88 -29
- package/dist/scenarios/scenarioStore.js.map +1 -1
- package/dist/security/headerSanitizer.js +52 -10
- package/dist/security/headerSanitizer.js.map +1 -1
- package/dist/security/secrets.js +10 -5
- package/dist/security/secrets.js.map +1 -1
- package/dist/server/accessControl.js +37 -10
- package/dist/server/accessControl.js.map +1 -1
- package/dist/server/createServer.js +1 -1
- package/dist/server/createServer.js.map +1 -1
- package/dist/server/eventsStore.js +15 -5
- package/dist/server/eventsStore.js.map +1 -1
- package/dist/server/routes.js +67 -19
- package/dist/server/routes.js.map +1 -1
- package/dist/server/sse.js +1 -1
- package/dist/server/sse.js.map +1 -1
- package/dist/setup/sdkDetector.js +7 -2
- package/dist/setup/sdkDetector.js.map +1 -1
- package/dist/setup/setupGenerator.js +136 -48
- package/dist/setup/setupGenerator.js.map +1 -1
- package/dist/state/stateExtractor.js +3 -1
- package/dist/state/stateExtractor.js.map +1 -1
- package/dist/state/stateResolver.js +10 -7
- package/dist/state/stateResolver.js.map +1 -1
- package/dist/state/stateStore.js +13 -4
- package/dist/state/stateStore.js.map +1 -1
- package/dist/storage/fileStore.js +12 -4
- package/dist/storage/fileStore.js.map +1 -1
- package/dist/teamControl/controlPlane.js +846 -174
- package/dist/teamControl/controlPlane.js.map +1 -1
- package/dist/teamControl/deployment.js +19 -5
- package/dist/teamControl/deployment.js.map +1 -1
- package/dist/teamControl/index.d.ts +4 -4
- package/dist/teamControl/index.js +2 -2
- package/dist/teamControl/index.js.map +1 -1
- package/dist/tests/testGenerator.js +15 -7
- package/dist/tests/testGenerator.js.map +1 -1
- package/dist/trust/index.d.ts +2 -2
- package/dist/trust/index.js +1 -1
- package/dist/trust/index.js.map +1 -1
- package/dist/trust/trustLadder.js +309 -63
- package/dist/trust/trustLadder.js.map +1 -1
- package/dist/worlds/index.d.ts +2 -2
- package/dist/worlds/index.js +1 -1
- package/dist/worlds/index.js.map +1 -1
- package/dist/worlds/worlds.js +364 -65
- package/dist/worlds/worlds.js.map +1 -1
- package/docs/assets/dashboard.png +0 -0
- package/docs/assets/ghostapi-avatar.png +0 -0
- package/docs/ci.md +49 -49
- package/docs/github-actions.md +84 -84
- package/docs/mcp.md +58 -52
- package/docs/policy.md +63 -61
- package/docs/providers/authoring-packs.md +180 -175
- package/docs/providers/stripe-core-pack.md +84 -82
- package/docs/releases/README.md +13 -0
- package/docs/security/action-gateway-threat-model.md +34 -34
- package/docs/security/action-ledger-incident-replay-threat-model.md +42 -42
- package/docs/security/approval-inbox-threat-model.md +35 -35
- package/docs/security/credential-broker-threat-model.md +52 -52
- package/docs/security/egress-threat-model.md +126 -126
- package/docs/security/inventory-threat-model.md +104 -104
- package/docs/security/kill-switch-budgets-threat-model.md +37 -37
- package/docs/security/reliability-threat-model.md +111 -111
- package/docs/security/trust-ladder-threat-model.md +33 -33
- package/docs/telemetry.md +14 -0
- package/docs/usage.md +680 -571
- package/examples/README.md +10 -10
- package/examples/agent-instructions/README.md +39 -39
- package/examples/ci-smoke/README.md +21 -21
- package/examples/ci-smoke/ghostapi.policy.yaml +22 -22
- package/examples/ci-smoke/package.json +9 -9
- package/examples/ci-smoke/production-egress.mjs +4 -2
- package/examples/ci-smoke/safe.mjs +21 -17
- package/examples/evals/README.md +24 -24
- package/examples/evals/retry-after.eval.json +71 -52
- package/examples/generic-rest/README.md +27 -27
- package/examples/github-fetch/README.md +38 -35
- package/examples/openai-streaming/README.md +16 -16
- package/examples/openai-streaming/streaming-tool-call.mjs +44 -31
- package/examples/policy/ghostapi.policy.yaml +27 -27
- package/examples/record-replay/README.md +19 -19
- package/examples/record-replay/replay-requests.json +22 -22
- package/examples/record-replay/stripe-checkout.har.json +30 -30
- package/examples/resend-node/README.md +22 -22
- package/examples/stripe-node/README.md +35 -35
- package/examples/stripe-node/checkout-flow.mjs +87 -61
- package/examples/twilio-node/README.md +21 -21
- package/examples/worlds/README.md +19 -19
- package/examples/worlds/subscription-recovery.mjs +21 -12
- package/package.json +28 -6
- package/dist/landing/assets/index-BXR41fAD.css +0 -1
- package/dist/landing/assets/index-CxWsopzP.css +0 -1
- package/dist/landing/assets/index-D8_ggMRE.js +0 -55
- package/dist/landing/assets/index-DZlpyhqd.js +0 -55
- package/dist/landing/assets/index-Dk-Xezs3.js +0 -55
- package/dist/landing/index.html +0 -15
- package/dist/landing/landing.d.ts +0 -3
- package/dist/landing/landing.js +0 -12
- package/dist/landing/landing.js.map +0 -1
- package/docs/commercial/README.md +0 -29
- package/docs/commercial/data-inventory.md +0 -36
- package/docs/commercial/manual-invoicing.md +0 -44
- package/docs/commercial/metrics.md +0 -51
- package/docs/commercial/pricing.md +0 -103
- package/docs/commercial/privacy-policy-draft.md +0 -41
- package/docs/commercial/terms-topics-for-counsel.md +0 -51
- package/docs/design-partners/README.md +0 -52
- package/docs/design-partners/demo-narratives.md +0 -33
- package/docs/design-partners/design-partner-offer.md +0 -39
- package/docs/design-partners/discovery-questions.md +0 -28
- package/docs/design-partners/feedback-capture-template.md +0 -48
- package/docs/design-partners/icp-one-pager.md +0 -43
- package/docs/design-partners/onboarding-checklist.md +0 -33
- package/docs/design-partners/pilot-success-criteria.md +0 -27
- package/docs/design-partners/pricing-interview-script.md +0 -18
- package/docs/design-partners/telemetry-plan.md +0 -48
- package/docs/development/baseline.md +0 -114
- package/docs/development/onboarding-smoke.md +0 -37
- package/docs/development/verification-0.1.8.md +0 -25
- package/docs/enterprise-product-roadmap-ru.md +0 -1879
- package/docs/fundraising/README.md +0 -87
- package/docs/fundraising/data-room-checklist.md +0 -72
- package/docs/fundraising/demo-script.md +0 -52
- package/docs/fundraising/design-partners-50.md +0 -74
- package/docs/fundraising/launch-posts.md +0 -37
- package/docs/fundraising/metrics-and-evidence.md +0 -30
- package/docs/fundraising/roadmap-12-month.md +0 -25
- package/docs/fundraising/technical-due-diligence-index.md +0 -37
- package/docs/fundraising/yc-application.md +0 -25
- package/docs/hosted-pilot.md +0 -111
- package/docs/operations/disaster-recovery-runbook.md +0 -81
- package/docs/operations/kill-switch-runbook.md +0 -32
- package/docs/release-checklist.md +0 -83
- package/docs/release-migration-and-rollback.md +0 -63
- package/docs/release-readiness.md +0 -39
- package/docs/team-control-plane.md +0 -72
package/dist/inventory/types.js
CHANGED
|
@@ -2,13 +2,42 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { sanitizeSecretString } from "../security/secrets.js";
|
|
3
3
|
export const INVENTORY_SCHEMA_VERSION = 1;
|
|
4
4
|
export const INVENTORY_KIND = "ghostapi.inventory";
|
|
5
|
-
export const IMPORT_SOURCE_TYPES = [
|
|
5
|
+
export const IMPORT_SOURCE_TYPES = [
|
|
6
|
+
"config",
|
|
7
|
+
"ci",
|
|
8
|
+
"gateway",
|
|
9
|
+
"cloud",
|
|
10
|
+
];
|
|
6
11
|
export const TOOL_KINDS = ["mcp_server", "sdk_client", "http_client"];
|
|
7
12
|
export const IDENTITY_ROLES = ["service_account", "user", "workload"];
|
|
8
|
-
export const SIDE_EFFECT_KINDS = [
|
|
13
|
+
export const SIDE_EFFECT_KINDS = [
|
|
14
|
+
"read",
|
|
15
|
+
"create",
|
|
16
|
+
"update",
|
|
17
|
+
"communicate",
|
|
18
|
+
"money_movement",
|
|
19
|
+
"delete",
|
|
20
|
+
"permission_change",
|
|
21
|
+
"deployment",
|
|
22
|
+
];
|
|
9
23
|
export const IMPACT_SEVERITIES = ["low", "medium", "high", "critical"];
|
|
10
|
-
export const EDGE_RELATIONS = [
|
|
11
|
-
|
|
24
|
+
export const EDGE_RELATIONS = [
|
|
25
|
+
"owns",
|
|
26
|
+
"uses",
|
|
27
|
+
"connects",
|
|
28
|
+
"exposes",
|
|
29
|
+
"permits",
|
|
30
|
+
"requires",
|
|
31
|
+
];
|
|
32
|
+
export const NODE_KINDS = [
|
|
33
|
+
"agent",
|
|
34
|
+
"identity",
|
|
35
|
+
"tool",
|
|
36
|
+
"provider",
|
|
37
|
+
"resource",
|
|
38
|
+
"side_effect",
|
|
39
|
+
"credential",
|
|
40
|
+
];
|
|
12
41
|
export const FINDING_KINDS = [
|
|
13
42
|
"orphaned_agents",
|
|
14
43
|
"stale_unused_credentials",
|
|
@@ -17,13 +46,33 @@ export const FINDING_KINDS = [
|
|
|
17
46
|
"agents_outside_gateway",
|
|
18
47
|
"missing_kill_switch",
|
|
19
48
|
"missing_evidence",
|
|
20
|
-
"policy_drift"
|
|
49
|
+
"policy_drift",
|
|
21
50
|
];
|
|
22
51
|
export const FINDING_BASIS = ["evidence", "heuristic"];
|
|
23
|
-
export const FINDING_SEVERITIES = [
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
52
|
+
export const FINDING_SEVERITIES = [
|
|
53
|
+
"critical",
|
|
54
|
+
"high",
|
|
55
|
+
"medium",
|
|
56
|
+
"low",
|
|
57
|
+
];
|
|
58
|
+
export const REMEDIATION_KINDS = [
|
|
59
|
+
"assign_owner",
|
|
60
|
+
"reduce_scope",
|
|
61
|
+
"revoke",
|
|
62
|
+
"onboard_through_gateway",
|
|
63
|
+
"create_eval",
|
|
64
|
+
];
|
|
65
|
+
export const REMEDIATION_STATUSES = [
|
|
66
|
+
"proposed",
|
|
67
|
+
"applied",
|
|
68
|
+
"rejected",
|
|
69
|
+
"expired",
|
|
70
|
+
];
|
|
71
|
+
export const FRESHNESS_DEFAULT_DAYS = {
|
|
72
|
+
edgeStale: 90,
|
|
73
|
+
credentialStale: 90,
|
|
74
|
+
evidenceFresh: 30,
|
|
75
|
+
};
|
|
27
76
|
const IDENTIFIER = /^[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
28
77
|
const SCOPE_IDENTIFIER = /^[a-z0-9][a-z0-9._:\-]{0,127}$/;
|
|
29
78
|
const HASH = /^[a-f0-9]{64}$/;
|
|
@@ -45,7 +94,7 @@ export const INVENTORY_LIMITS = {
|
|
|
45
94
|
maxRemediations: 1000,
|
|
46
95
|
maxReferencesPerRecord: 64,
|
|
47
96
|
maxScopesPerRecord: 32,
|
|
48
|
-
maxCounters: 3
|
|
97
|
+
maxCounters: 3,
|
|
49
98
|
};
|
|
50
99
|
export class InventoryError extends Error {
|
|
51
100
|
constructor(message) {
|
|
@@ -69,7 +118,7 @@ export function emptyInventoryState() {
|
|
|
69
118
|
policies: [],
|
|
70
119
|
edges: [],
|
|
71
120
|
findings: [],
|
|
72
|
-
remediations: []
|
|
121
|
+
remediations: [],
|
|
73
122
|
};
|
|
74
123
|
}
|
|
75
124
|
export function sha256Digest(value) {
|
|
@@ -82,30 +131,74 @@ export function sortedKeysReplacer(_key, value) {
|
|
|
82
131
|
if (Array.isArray(value))
|
|
83
132
|
return value;
|
|
84
133
|
if (value !== null && typeof value === "object") {
|
|
85
|
-
return Object.fromEntries(Object.entries(value).sort(([a], [b]) =>
|
|
134
|
+
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
|
|
86
135
|
}
|
|
87
136
|
return value;
|
|
88
137
|
}
|
|
89
138
|
export function validateImportPayload(value) {
|
|
90
139
|
const payload = object(value, "Inventory import must be an object.");
|
|
91
|
-
exactKeys(payload, [
|
|
92
|
-
"
|
|
140
|
+
exactKeys(payload, [
|
|
141
|
+
"schemaVersion",
|
|
142
|
+
"kind",
|
|
143
|
+
"source",
|
|
144
|
+
"expectedPolicyHashes",
|
|
145
|
+
"counters",
|
|
146
|
+
"agents",
|
|
147
|
+
"tools",
|
|
148
|
+
"identities",
|
|
149
|
+
"providers",
|
|
150
|
+
"resources",
|
|
151
|
+
"sideEffects",
|
|
152
|
+
"credentials",
|
|
153
|
+
"policies",
|
|
154
|
+
], "inventory import", [
|
|
155
|
+
"expectedPolicyHashes",
|
|
156
|
+
"counters",
|
|
157
|
+
"agents",
|
|
158
|
+
"tools",
|
|
159
|
+
"identities",
|
|
160
|
+
"providers",
|
|
161
|
+
"resources",
|
|
162
|
+
"sideEffects",
|
|
163
|
+
"credentials",
|
|
164
|
+
"policies",
|
|
93
165
|
]);
|
|
94
|
-
if (payload.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
|
|
166
|
+
if (payload.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
|
|
167
|
+
payload.kind !== "ghostapi.inventory-import")
|
|
95
168
|
throw new InventoryError("Unsupported inventory import schema.");
|
|
96
169
|
const source = validateImportSource(payload.source);
|
|
97
170
|
if (source.sourceType === "config" && payload.counters !== undefined)
|
|
98
171
|
throw new InventoryError("Counters are only allowed for ci, gateway, or cloud imports.");
|
|
99
|
-
const expectedPolicyHashes = payload.expectedPolicyHashes === undefined
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
172
|
+
const expectedPolicyHashes = payload.expectedPolicyHashes === undefined
|
|
173
|
+
? []
|
|
174
|
+
: array(payload.expectedPolicyHashes, "expected policy hashes", 64).map(validateExpectedPolicyHash);
|
|
175
|
+
const counters = payload.counters === undefined
|
|
176
|
+
? undefined
|
|
177
|
+
: validateCounters(payload.counters);
|
|
178
|
+
const agents = payload.agents === undefined
|
|
179
|
+
? []
|
|
180
|
+
: array(payload.agents, "agents", 200).map(validateImportedAgent);
|
|
181
|
+
const tools = payload.tools === undefined
|
|
182
|
+
? []
|
|
183
|
+
: array(payload.tools, "tools", 200).map(validateImportedTool);
|
|
184
|
+
const identities = payload.identities === undefined
|
|
185
|
+
? []
|
|
186
|
+
: array(payload.identities, "identities", 200).map(validateImportedIdentity);
|
|
187
|
+
const providers = payload.providers === undefined
|
|
188
|
+
? []
|
|
189
|
+
: array(payload.providers, "providers", 100).map(validateImportedProvider);
|
|
190
|
+
const resources = payload.resources === undefined
|
|
191
|
+
? []
|
|
192
|
+
: array(payload.resources, "resources", 300).map(validateImportedResource);
|
|
193
|
+
const sideEffects = payload.sideEffects === undefined
|
|
194
|
+
? []
|
|
195
|
+
: array(payload.sideEffects, "side effects", 400).map(validateImportedSideEffect);
|
|
196
|
+
const credentials = payload.credentials === undefined
|
|
197
|
+
? []
|
|
198
|
+
: array(payload.credentials, "credentials", 200).map(validateImportedCredential);
|
|
199
|
+
const policies = payload.policies === undefined
|
|
200
|
+
? []
|
|
201
|
+
: array(payload.policies, "policies", 50).map(validateImportedPolicy);
|
|
109
202
|
unique(agents.map((agent) => agent.agentId), "agent ids");
|
|
110
203
|
unique(tools.map((tool) => tool.toolId), "tool ids");
|
|
111
204
|
unique(identities.map((identity) => identity.identityId), "identity ids");
|
|
@@ -127,13 +220,30 @@ export function validateImportPayload(value) {
|
|
|
127
220
|
resources,
|
|
128
221
|
sideEffects,
|
|
129
222
|
credentials,
|
|
130
|
-
policies
|
|
223
|
+
policies,
|
|
131
224
|
};
|
|
132
225
|
}
|
|
133
226
|
export function validateState(value) {
|
|
134
227
|
const state = object(value, "Inventory store must be an object.");
|
|
135
|
-
exactKeys(state, [
|
|
136
|
-
|
|
228
|
+
exactKeys(state, [
|
|
229
|
+
"schemaVersion",
|
|
230
|
+
"kind",
|
|
231
|
+
"sources",
|
|
232
|
+
"importRuns",
|
|
233
|
+
"agents",
|
|
234
|
+
"tools",
|
|
235
|
+
"identities",
|
|
236
|
+
"providers",
|
|
237
|
+
"resources",
|
|
238
|
+
"sideEffects",
|
|
239
|
+
"credentials",
|
|
240
|
+
"policies",
|
|
241
|
+
"edges",
|
|
242
|
+
"findings",
|
|
243
|
+
"remediations",
|
|
244
|
+
], "inventory store");
|
|
245
|
+
if (state.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
|
|
246
|
+
state.kind !== INVENTORY_KIND)
|
|
137
247
|
throw new InventoryError("Unsupported inventory store schema.");
|
|
138
248
|
const sources = array(state.sources, "import sources", INVENTORY_LIMITS.maxSources).map(validateSourceRecord);
|
|
139
249
|
const importRuns = array(state.importRuns, "import runs", INVENTORY_LIMITS.maxImports).map(validateImportRunRecord);
|
|
@@ -148,7 +258,23 @@ export function validateState(value) {
|
|
|
148
258
|
const edges = array(state.edges, "graph edges", INVENTORY_LIMITS.maxEdges).map(validateEdge);
|
|
149
259
|
const findings = array(state.findings, "findings", INVENTORY_LIMITS.maxFindings).map(validateFindingRecord);
|
|
150
260
|
const remediations = array(state.remediations, "remediations", INVENTORY_LIMITS.maxRemediations).map(validateRemediationRecord);
|
|
151
|
-
return {
|
|
261
|
+
return {
|
|
262
|
+
schemaVersion: 1,
|
|
263
|
+
kind: "ghostapi.inventory",
|
|
264
|
+
sources,
|
|
265
|
+
importRuns,
|
|
266
|
+
agents,
|
|
267
|
+
tools,
|
|
268
|
+
identities,
|
|
269
|
+
providers,
|
|
270
|
+
resources,
|
|
271
|
+
sideEffects,
|
|
272
|
+
credentials,
|
|
273
|
+
policies,
|
|
274
|
+
edges,
|
|
275
|
+
findings,
|
|
276
|
+
remediations,
|
|
277
|
+
};
|
|
152
278
|
}
|
|
153
279
|
export function clone(value) {
|
|
154
280
|
return structuredClone(value);
|
|
@@ -166,7 +292,7 @@ export function validateOperator(value) {
|
|
|
166
292
|
id: identifier(operator.id, "Inventory operator id"),
|
|
167
293
|
principalId: identifier(operator.principalId, "Inventory operator principal id"),
|
|
168
294
|
tenantId: identifier(operator.tenantId, "Inventory operator tenant id"),
|
|
169
|
-
permissions
|
|
295
|
+
permissions,
|
|
170
296
|
};
|
|
171
297
|
}
|
|
172
298
|
export function validateFindingsAndRemediations(value) {
|
|
@@ -174,11 +300,15 @@ export function validateFindingsAndRemediations(value) {
|
|
|
174
300
|
exactKeys(box, ["findings", "remediations"], "inventory findings box");
|
|
175
301
|
return {
|
|
176
302
|
findings: array(box.findings, "findings", INVENTORY_LIMITS.maxFindings).map(validateFindingRecord),
|
|
177
|
-
remediations: array(box.remediations, "remediations", INVENTORY_LIMITS.maxRemediations).map(validateRemediationRecord)
|
|
303
|
+
remediations: array(box.remediations, "remediations", INVENTORY_LIMITS.maxRemediations).map(validateRemediationRecord),
|
|
178
304
|
};
|
|
179
305
|
}
|
|
180
306
|
function isInventoryPermission(value) {
|
|
181
|
-
return value === "inventory.import" ||
|
|
307
|
+
return (value === "inventory.import" ||
|
|
308
|
+
value === "inventory.inspect" ||
|
|
309
|
+
value === "inventory.analyze" ||
|
|
310
|
+
value === "inventory.remediate" ||
|
|
311
|
+
value === "inventory.export");
|
|
182
312
|
}
|
|
183
313
|
function validateImportSource(value) {
|
|
184
314
|
const source = object(value, "Inventory import source is invalid.");
|
|
@@ -190,19 +320,34 @@ function validateImportSource(value) {
|
|
|
190
320
|
sourceId: identifier(source.sourceId, "Import source id"),
|
|
191
321
|
sourceType: sourceType,
|
|
192
322
|
sourceName: text(source.sourceName, "Import source name", 120),
|
|
193
|
-
...(source.sourceVersion === undefined
|
|
323
|
+
...(source.sourceVersion === undefined
|
|
324
|
+
? {}
|
|
325
|
+
: {
|
|
326
|
+
sourceVersion: text(source.sourceVersion, "Import source version", 64),
|
|
327
|
+
}),
|
|
194
328
|
};
|
|
195
329
|
}
|
|
196
330
|
function validateExpectedPolicyHash(value) {
|
|
197
331
|
const expected = object(value, "Expected policy hash is invalid.");
|
|
198
332
|
exactKeys(expected, ["environmentId", "policyHash"], "expected policy hash");
|
|
199
|
-
return {
|
|
333
|
+
return {
|
|
334
|
+
environmentId: identifier(expected.environmentId, "Environment id"),
|
|
335
|
+
policyHash: hash(expected.policyHash, "Policy hash"),
|
|
336
|
+
};
|
|
200
337
|
}
|
|
201
338
|
function validateCounters(value) {
|
|
202
339
|
const counters = object(value, "Import counters are invalid.");
|
|
203
|
-
exactKeys(counters, [
|
|
340
|
+
exactKeys(counters, [
|
|
341
|
+
"preventedEgressAttempts",
|
|
342
|
+
"reviewTimeMinutes",
|
|
343
|
+
"meanTimeToContainMinutes",
|
|
344
|
+
], "import counters");
|
|
204
345
|
const result = {};
|
|
205
|
-
for (const key of [
|
|
346
|
+
for (const key of [
|
|
347
|
+
"preventedEgressAttempts",
|
|
348
|
+
"reviewTimeMinutes",
|
|
349
|
+
"meanTimeToContainMinutes",
|
|
350
|
+
]) {
|
|
206
351
|
if (counters[key] !== undefined)
|
|
207
352
|
result[key] = nonNegative(counters[key], `Import counter ${key}`, 9_999_999_999);
|
|
208
353
|
}
|
|
@@ -210,24 +355,59 @@ function validateCounters(value) {
|
|
|
210
355
|
}
|
|
211
356
|
function validateImportedAgent(value) {
|
|
212
357
|
const record = object(value, "Imported agent is invalid.");
|
|
213
|
-
exactKeys(record, [
|
|
214
|
-
"
|
|
358
|
+
exactKeys(record, [
|
|
359
|
+
"agentId",
|
|
360
|
+
"name",
|
|
361
|
+
"ownerId",
|
|
362
|
+
"businessPurpose",
|
|
363
|
+
"model",
|
|
364
|
+
"runtime",
|
|
365
|
+
"version",
|
|
366
|
+
"identityIds",
|
|
367
|
+
"environmentIds",
|
|
368
|
+
"gatewayManaged",
|
|
369
|
+
"killSwitchEnabled",
|
|
370
|
+
"lastEvidenceAt",
|
|
371
|
+
], "imported agent", [
|
|
372
|
+
"ownerId",
|
|
373
|
+
"businessPurpose",
|
|
374
|
+
"model",
|
|
375
|
+
"runtime",
|
|
376
|
+
"version",
|
|
377
|
+
"lastEvidenceAt",
|
|
215
378
|
]);
|
|
216
|
-
if (typeof record.gatewayManaged !== "boolean" ||
|
|
379
|
+
if (typeof record.gatewayManaged !== "boolean" ||
|
|
380
|
+
typeof record.killSwitchEnabled !== "boolean")
|
|
217
381
|
throw new InventoryError("Imported agent booleans are invalid.");
|
|
218
382
|
return {
|
|
219
383
|
agentId: identifier(record.agentId, "Agent id"),
|
|
220
384
|
name: text(record.name, "Agent name", 160),
|
|
221
|
-
...(record.ownerId === undefined
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
...(record.
|
|
225
|
-
|
|
385
|
+
...(record.ownerId === undefined
|
|
386
|
+
? {}
|
|
387
|
+
: { ownerId: identifier(record.ownerId, "Agent owner id") }),
|
|
388
|
+
...(record.businessPurpose === undefined
|
|
389
|
+
? {}
|
|
390
|
+
: {
|
|
391
|
+
businessPurpose: text(record.businessPurpose, "Agent business purpose", 240),
|
|
392
|
+
}),
|
|
393
|
+
...(record.model === undefined
|
|
394
|
+
? {}
|
|
395
|
+
: { model: text(record.model, "Agent model", 120) }),
|
|
396
|
+
...(record.runtime === undefined
|
|
397
|
+
? {}
|
|
398
|
+
: { runtime: text(record.runtime, "Agent runtime", 120) }),
|
|
399
|
+
...(record.version === undefined
|
|
400
|
+
? {}
|
|
401
|
+
: { version: text(record.version, "Agent version", 64) }),
|
|
226
402
|
identityIds: identifiers(record.identityIds, "Agent identity ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
227
403
|
environmentIds: identifiers(record.environmentIds, "Agent environment ids", 16),
|
|
228
404
|
gatewayManaged: record.gatewayManaged,
|
|
229
405
|
killSwitchEnabled: record.killSwitchEnabled,
|
|
230
|
-
...(record.lastEvidenceAt === undefined
|
|
406
|
+
...(record.lastEvidenceAt === undefined
|
|
407
|
+
? {}
|
|
408
|
+
: {
|
|
409
|
+
lastEvidenceAt: timestamp(record.lastEvidenceAt, "Agent last evidence time"),
|
|
410
|
+
}),
|
|
231
411
|
};
|
|
232
412
|
}
|
|
233
413
|
function validateImportedTool(value) {
|
|
@@ -240,14 +420,24 @@ function validateImportedTool(value) {
|
|
|
240
420
|
toolId: identifier(record.toolId, "Tool id"),
|
|
241
421
|
name: text(record.name, "Tool name", 160),
|
|
242
422
|
kind: kind,
|
|
243
|
-
...(record.providerId === undefined
|
|
423
|
+
...(record.providerId === undefined
|
|
424
|
+
? {}
|
|
425
|
+
: { providerId: identifier(record.providerId, "Tool provider id") }),
|
|
244
426
|
requiredScopes: scopes(record.requiredScopes, "Tool required scopes", INVENTORY_LIMITS.maxScopesPerRecord),
|
|
245
|
-
credentialIds: identifiers(record.credentialIds, "Tool credential ids", INVENTORY_LIMITS.maxReferencesPerRecord)
|
|
427
|
+
credentialIds: identifiers(record.credentialIds, "Tool credential ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
246
428
|
};
|
|
247
429
|
}
|
|
248
430
|
function validateImportedIdentity(value) {
|
|
249
431
|
const record = object(value, "Imported identity is invalid.");
|
|
250
|
-
exactKeys(record, [
|
|
432
|
+
exactKeys(record, [
|
|
433
|
+
"identityId",
|
|
434
|
+
"principalId",
|
|
435
|
+
"role",
|
|
436
|
+
"agentId",
|
|
437
|
+
"toolIds",
|
|
438
|
+
"environmentIds",
|
|
439
|
+
"scopes",
|
|
440
|
+
], "imported identity", ["agentId"]);
|
|
251
441
|
const role = record.role;
|
|
252
442
|
if (!IDENTITY_ROLES.includes(role))
|
|
253
443
|
throw new InventoryError("Imported identity role is invalid.");
|
|
@@ -255,31 +445,55 @@ function validateImportedIdentity(value) {
|
|
|
255
445
|
identityId: identifier(record.identityId, "Identity id"),
|
|
256
446
|
principalId: identifier(record.principalId, "Identity principal id"),
|
|
257
447
|
role: role,
|
|
258
|
-
...(record.agentId === undefined
|
|
448
|
+
...(record.agentId === undefined
|
|
449
|
+
? {}
|
|
450
|
+
: { agentId: identifier(record.agentId, "Identity agent id") }),
|
|
259
451
|
toolIds: identifiers(record.toolIds, "Identity tool ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
260
452
|
environmentIds: identifiers(record.environmentIds, "Identity environment ids", 16),
|
|
261
|
-
scopes: scopes(record.scopes, "Identity scopes", INVENTORY_LIMITS.maxScopesPerRecord)
|
|
453
|
+
scopes: scopes(record.scopes, "Identity scopes", INVENTORY_LIMITS.maxScopesPerRecord),
|
|
262
454
|
};
|
|
263
455
|
}
|
|
264
456
|
function validateImportedProvider(value) {
|
|
265
457
|
const record = object(value, "Imported provider is invalid.");
|
|
266
|
-
exactKeys(record, [
|
|
267
|
-
|
|
458
|
+
exactKeys(record, [
|
|
459
|
+
"providerId",
|
|
460
|
+
"name",
|
|
461
|
+
"providerType",
|
|
462
|
+
"ownerId",
|
|
463
|
+
"environmentIds",
|
|
464
|
+
"resourceIds",
|
|
465
|
+
"gatewayBound",
|
|
466
|
+
"killSwitchApplied",
|
|
467
|
+
], "imported provider", ["ownerId"]);
|
|
468
|
+
if (typeof record.gatewayBound !== "boolean" ||
|
|
469
|
+
typeof record.killSwitchApplied !== "boolean")
|
|
268
470
|
throw new InventoryError("Imported provider booleans are invalid.");
|
|
269
471
|
return {
|
|
270
472
|
providerId: identifier(record.providerId, "Provider id"),
|
|
271
473
|
name: text(record.name, "Provider name", 160),
|
|
272
474
|
providerType: text(record.providerType, "Provider type", 120),
|
|
273
|
-
...(record.ownerId === undefined
|
|
475
|
+
...(record.ownerId === undefined
|
|
476
|
+
? {}
|
|
477
|
+
: { ownerId: identifier(record.ownerId, "Provider owner id") }),
|
|
274
478
|
environmentIds: identifiers(record.environmentIds, "Provider environment ids", 16),
|
|
275
479
|
resourceIds: identifiers(record.resourceIds, "Provider resource ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
276
480
|
gatewayBound: record.gatewayBound,
|
|
277
|
-
killSwitchApplied: record.killSwitchApplied
|
|
481
|
+
killSwitchApplied: record.killSwitchApplied,
|
|
278
482
|
};
|
|
279
483
|
}
|
|
280
484
|
function validateImportedResource(value) {
|
|
281
485
|
const record = object(value, "Imported resource is invalid.");
|
|
282
|
-
exactKeys(record, [
|
|
486
|
+
exactKeys(record, [
|
|
487
|
+
"resourceId",
|
|
488
|
+
"providerId",
|
|
489
|
+
"resourceType",
|
|
490
|
+
"name",
|
|
491
|
+
"environmentIds",
|
|
492
|
+
"sensitive",
|
|
493
|
+
"ownerId",
|
|
494
|
+
"businessPurpose",
|
|
495
|
+
"sideEffectIds",
|
|
496
|
+
], "imported resource", ["ownerId", "businessPurpose"]);
|
|
283
497
|
if (typeof record.sensitive !== "boolean")
|
|
284
498
|
throw new InventoryError("Imported resource sensitive flag is invalid.");
|
|
285
499
|
return {
|
|
@@ -289,9 +503,15 @@ function validateImportedResource(value) {
|
|
|
289
503
|
name: text(record.name, "Resource name", 160),
|
|
290
504
|
environmentIds: identifiers(record.environmentIds, "Resource environment ids", 16),
|
|
291
505
|
sensitive: record.sensitive,
|
|
292
|
-
...(record.ownerId === undefined
|
|
293
|
-
|
|
294
|
-
|
|
506
|
+
...(record.ownerId === undefined
|
|
507
|
+
? {}
|
|
508
|
+
: { ownerId: identifier(record.ownerId, "Resource owner id") }),
|
|
509
|
+
...(record.businessPurpose === undefined
|
|
510
|
+
? {}
|
|
511
|
+
: {
|
|
512
|
+
businessPurpose: text(record.businessPurpose, "Resource business purpose", 240),
|
|
513
|
+
}),
|
|
514
|
+
sideEffectIds: identifiers(record.sideEffectIds, "Resource side effect ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
295
515
|
};
|
|
296
516
|
}
|
|
297
517
|
function validateImportedSideEffect(value) {
|
|
@@ -307,14 +527,25 @@ function validateImportedSideEffect(value) {
|
|
|
307
527
|
sideEffectId: identifier(record.sideEffectId, "Side effect id"),
|
|
308
528
|
resourceId: identifier(record.resourceId, "Side effect resource id"),
|
|
309
529
|
kind: kind,
|
|
310
|
-
severity: severity
|
|
530
|
+
severity: severity,
|
|
311
531
|
};
|
|
312
532
|
}
|
|
313
533
|
function validateImportedCredential(value) {
|
|
314
534
|
const record = object(value, "Imported credential is invalid.");
|
|
315
|
-
exactKeys(record, [
|
|
316
|
-
"
|
|
317
|
-
|
|
535
|
+
exactKeys(record, [
|
|
536
|
+
"credentialId",
|
|
537
|
+
"name",
|
|
538
|
+
"providerId",
|
|
539
|
+
"ownerId",
|
|
540
|
+
"environmentIds",
|
|
541
|
+
"grantScopes",
|
|
542
|
+
"toolIds",
|
|
543
|
+
"status",
|
|
544
|
+
"issuedAt",
|
|
545
|
+
"lastUsedAt",
|
|
546
|
+
"rotatedAt",
|
|
547
|
+
"gatewayBound",
|
|
548
|
+
], "imported credential", ["ownerId", "lastUsedAt", "rotatedAt"]);
|
|
318
549
|
if (record.status !== "active" && record.status !== "revoked")
|
|
319
550
|
throw new InventoryError("Imported credential status is invalid.");
|
|
320
551
|
if (typeof record.gatewayBound !== "boolean")
|
|
@@ -323,15 +554,23 @@ function validateImportedCredential(value) {
|
|
|
323
554
|
credentialId: identifier(record.credentialId, "Credential id"),
|
|
324
555
|
name: text(record.name, "Credential name", 160),
|
|
325
556
|
providerId: identifier(record.providerId, "Credential provider id"),
|
|
326
|
-
...(record.ownerId === undefined
|
|
557
|
+
...(record.ownerId === undefined
|
|
558
|
+
? {}
|
|
559
|
+
: { ownerId: identifier(record.ownerId, "Credential owner id") }),
|
|
327
560
|
environmentIds: identifiers(record.environmentIds, "Credential environment ids", 16),
|
|
328
561
|
grantScopes: scopes(record.grantScopes, "Credential grant scopes", INVENTORY_LIMITS.maxScopesPerRecord),
|
|
329
562
|
toolIds: identifiers(record.toolIds, "Credential tool ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
330
563
|
status: record.status,
|
|
331
564
|
issuedAt: timestamp(record.issuedAt, "Credential issued time"),
|
|
332
|
-
...(record.lastUsedAt === undefined
|
|
333
|
-
|
|
334
|
-
|
|
565
|
+
...(record.lastUsedAt === undefined
|
|
566
|
+
? {}
|
|
567
|
+
: {
|
|
568
|
+
lastUsedAt: timestamp(record.lastUsedAt, "Credential last used time"),
|
|
569
|
+
}),
|
|
570
|
+
...(record.rotatedAt === undefined
|
|
571
|
+
? {}
|
|
572
|
+
: { rotatedAt: timestamp(record.rotatedAt, "Credential rotated time") }),
|
|
573
|
+
gatewayBound: record.gatewayBound,
|
|
335
574
|
};
|
|
336
575
|
}
|
|
337
576
|
function validateImportedPolicy(value) {
|
|
@@ -342,12 +581,21 @@ function validateImportedPolicy(value) {
|
|
|
342
581
|
name: text(record.name, "Policy name", 160),
|
|
343
582
|
version: text(record.version, "Policy version", 64),
|
|
344
583
|
hash: hash(record.hash, "Policy hash"),
|
|
345
|
-
environmentIds: identifiers(record.environmentIds, "Policy environment ids", 16)
|
|
584
|
+
environmentIds: identifiers(record.environmentIds, "Policy environment ids", 16),
|
|
346
585
|
};
|
|
347
586
|
}
|
|
348
587
|
function validateSourceRecord(value) {
|
|
349
588
|
const record = object(value, "Import source is invalid.");
|
|
350
|
-
exactKeys(record, [
|
|
589
|
+
exactKeys(record, [
|
|
590
|
+
"tenantId",
|
|
591
|
+
"sourceId",
|
|
592
|
+
"sourceType",
|
|
593
|
+
"sourceName",
|
|
594
|
+
"sourceVersion",
|
|
595
|
+
"firstSeenAt",
|
|
596
|
+
"lastSeenAt",
|
|
597
|
+
"lastDigest",
|
|
598
|
+
], "import source", ["sourceVersion"]);
|
|
351
599
|
const sourceType = record.sourceType;
|
|
352
600
|
if (!IMPORT_SOURCE_TYPES.includes(sourceType))
|
|
353
601
|
throw new InventoryError("Import source type is invalid.");
|
|
@@ -356,16 +604,34 @@ function validateSourceRecord(value) {
|
|
|
356
604
|
sourceId: identifier(record.sourceId, "Source id"),
|
|
357
605
|
sourceType: sourceType,
|
|
358
606
|
sourceName: text(record.sourceName, "Source name", 120),
|
|
359
|
-
...(record.sourceVersion === undefined
|
|
607
|
+
...(record.sourceVersion === undefined
|
|
608
|
+
? {}
|
|
609
|
+
: { sourceVersion: text(record.sourceVersion, "Source version", 64) }),
|
|
360
610
|
firstSeenAt: timestamp(record.firstSeenAt, "Source first seen time"),
|
|
361
611
|
lastSeenAt: timestamp(record.lastSeenAt, "Source last seen time"),
|
|
362
|
-
lastDigest: hash(record.lastDigest, "Source digest")
|
|
612
|
+
lastDigest: hash(record.lastDigest, "Source digest"),
|
|
363
613
|
};
|
|
364
614
|
}
|
|
365
615
|
function validateImportRunRecord(value) {
|
|
366
616
|
const record = object(value, "Import run is invalid.");
|
|
367
|
-
exactKeys(record, [
|
|
368
|
-
|
|
617
|
+
exactKeys(record, [
|
|
618
|
+
"schemaVersion",
|
|
619
|
+
"kind",
|
|
620
|
+
"tenantId",
|
|
621
|
+
"runId",
|
|
622
|
+
"importedAt",
|
|
623
|
+
"importedBy",
|
|
624
|
+
"source",
|
|
625
|
+
"digest",
|
|
626
|
+
"expectedPolicyHashes",
|
|
627
|
+
"counters",
|
|
628
|
+
"recordCounts",
|
|
629
|
+
"edgesCreated",
|
|
630
|
+
"edgesRefreshed",
|
|
631
|
+
"status",
|
|
632
|
+
], "import run", ["counters"]);
|
|
633
|
+
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
|
|
634
|
+
record.kind !== "ghostapi.inventory-import-run")
|
|
369
635
|
throw new InventoryError("Unsupported import run schema.");
|
|
370
636
|
if (record.status !== "completed")
|
|
371
637
|
throw new InventoryError("Import run status is invalid.");
|
|
@@ -375,8 +641,19 @@ function validateImportRunRecord(value) {
|
|
|
375
641
|
if (!IMPORT_SOURCE_TYPES.includes(sourceType))
|
|
376
642
|
throw new InventoryError("Import run source type is invalid.");
|
|
377
643
|
const recordCounts = object(record.recordCounts, "Import run record counts are invalid.");
|
|
378
|
-
exactKeys(recordCounts, [
|
|
379
|
-
|
|
644
|
+
exactKeys(recordCounts, [
|
|
645
|
+
"agents",
|
|
646
|
+
"tools",
|
|
647
|
+
"identities",
|
|
648
|
+
"providers",
|
|
649
|
+
"resources",
|
|
650
|
+
"sideEffects",
|
|
651
|
+
"credentials",
|
|
652
|
+
"policies",
|
|
653
|
+
], "import run record counts");
|
|
654
|
+
const counters = record.counters === undefined
|
|
655
|
+
? undefined
|
|
656
|
+
: validateCounters(record.counters);
|
|
380
657
|
if (sourceType === "config" && counters !== undefined)
|
|
381
658
|
throw new InventoryError("Counters are only allowed for ci, gateway, or cloud imports.");
|
|
382
659
|
return {
|
|
@@ -390,7 +667,11 @@ function validateImportRunRecord(value) {
|
|
|
390
667
|
sourceId: identifier(source.sourceId, "Import run source id"),
|
|
391
668
|
sourceType: sourceType,
|
|
392
669
|
sourceName: text(source.sourceName, "Import run source name", 120),
|
|
393
|
-
...(source.sourceVersion === undefined
|
|
670
|
+
...(source.sourceVersion === undefined
|
|
671
|
+
? {}
|
|
672
|
+
: {
|
|
673
|
+
sourceVersion: text(source.sourceVersion, "Import run source version", 64),
|
|
674
|
+
}),
|
|
394
675
|
},
|
|
395
676
|
digest: hash(record.digest, "Import run digest"),
|
|
396
677
|
expectedPolicyHashes: array(record.expectedPolicyHashes, "import run expected policy hashes", 64).map(validateExpectedPolicyHash),
|
|
@@ -403,21 +684,39 @@ function validateImportRunRecord(value) {
|
|
|
403
684
|
resources: nonNegative(recordCounts.resources, "Import run resource count", 1000),
|
|
404
685
|
sideEffects: nonNegative(recordCounts.sideEffects, "Import run side effect count", 2000),
|
|
405
686
|
credentials: nonNegative(recordCounts.credentials, "Import run credential count", 500),
|
|
406
|
-
policies: nonNegative(recordCounts.policies, "Import run policy count", 100)
|
|
687
|
+
policies: nonNegative(recordCounts.policies, "Import run policy count", 100),
|
|
407
688
|
},
|
|
408
689
|
edgesCreated: nonNegative(record.edgesCreated, "Import run edges created", 6000),
|
|
409
690
|
edgesRefreshed: nonNegative(record.edgesRefreshed, "Import run edges refreshed", 6000),
|
|
410
|
-
status: "completed"
|
|
691
|
+
status: "completed",
|
|
411
692
|
};
|
|
412
693
|
}
|
|
413
694
|
function validateAgentRecord(value) {
|
|
414
695
|
const record = object(value, "Agent record is invalid.");
|
|
415
|
-
exactKeys(record, [
|
|
416
|
-
"
|
|
417
|
-
|
|
418
|
-
|
|
696
|
+
exactKeys(record, [
|
|
697
|
+
"schemaVersion",
|
|
698
|
+
"kind",
|
|
699
|
+
"tenantId",
|
|
700
|
+
"agentId",
|
|
701
|
+
"name",
|
|
702
|
+
"ownerId",
|
|
703
|
+
"businessPurpose",
|
|
704
|
+
"model",
|
|
705
|
+
"runtime",
|
|
706
|
+
"version",
|
|
707
|
+
"identityIds",
|
|
708
|
+
"environmentIds",
|
|
709
|
+
"gatewayManaged",
|
|
710
|
+
"killSwitchEnabled",
|
|
711
|
+
"lastEvidenceAt",
|
|
712
|
+
"provenance",
|
|
713
|
+
"freshness",
|
|
714
|
+
], "agent record", ["businessPurpose", "model", "runtime", "version"]);
|
|
715
|
+
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
|
|
716
|
+
record.kind !== "ghostapi.inventory-agent")
|
|
419
717
|
throw new InventoryError("Unsupported agent record schema.");
|
|
420
|
-
if (typeof record.gatewayManaged !== "boolean" ||
|
|
718
|
+
if (typeof record.gatewayManaged !== "boolean" ||
|
|
719
|
+
typeof record.killSwitchEnabled !== "boolean")
|
|
421
720
|
throw new InventoryError("Agent record booleans are invalid.");
|
|
422
721
|
return {
|
|
423
722
|
schemaVersion: 1,
|
|
@@ -425,23 +724,52 @@ function validateAgentRecord(value) {
|
|
|
425
724
|
tenantId: identifier(record.tenantId, "Agent tenant id"),
|
|
426
725
|
agentId: identifier(record.agentId, "Agent id"),
|
|
427
726
|
name: text(record.name, "Agent name", 160),
|
|
428
|
-
ownerId: record.ownerId === null
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
727
|
+
ownerId: record.ownerId === null
|
|
728
|
+
? null
|
|
729
|
+
: record.ownerId === undefined
|
|
730
|
+
? null
|
|
731
|
+
: identifier(record.ownerId, "Agent owner id"),
|
|
732
|
+
...(record.businessPurpose === undefined
|
|
733
|
+
? {}
|
|
734
|
+
: {
|
|
735
|
+
businessPurpose: text(record.businessPurpose, "Agent business purpose", 240),
|
|
736
|
+
}),
|
|
737
|
+
...(record.model === undefined
|
|
738
|
+
? {}
|
|
739
|
+
: { model: text(record.model, "Agent model", 120) }),
|
|
740
|
+
...(record.runtime === undefined
|
|
741
|
+
? {}
|
|
742
|
+
: { runtime: text(record.runtime, "Agent runtime", 120) }),
|
|
743
|
+
...(record.version === undefined
|
|
744
|
+
? {}
|
|
745
|
+
: { version: text(record.version, "Agent version", 64) }),
|
|
433
746
|
identityIds: identifiers(record.identityIds, "Agent identity ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
434
747
|
environmentIds: identifiers(record.environmentIds, "Agent environment ids", 16),
|
|
435
748
|
gatewayManaged: record.gatewayManaged,
|
|
436
749
|
killSwitchEnabled: record.killSwitchEnabled,
|
|
437
|
-
lastEvidenceAt: record.lastEvidenceAt === null
|
|
750
|
+
lastEvidenceAt: record.lastEvidenceAt === null
|
|
751
|
+
? null
|
|
752
|
+
: record.lastEvidenceAt === undefined
|
|
753
|
+
? null
|
|
754
|
+
: timestamp(record.lastEvidenceAt, "Agent last evidence time"),
|
|
438
755
|
provenance: validateProvenance(record.provenance),
|
|
439
|
-
freshness: validateFreshness(record.freshness)
|
|
756
|
+
freshness: validateFreshness(record.freshness),
|
|
440
757
|
};
|
|
441
758
|
}
|
|
442
759
|
function validateToolRecord(value) {
|
|
443
760
|
const record = object(value, "Tool record is invalid.");
|
|
444
|
-
exactKeys(record, [
|
|
761
|
+
exactKeys(record, [
|
|
762
|
+
"schemaVersion",
|
|
763
|
+
"kind",
|
|
764
|
+
"tenantId",
|
|
765
|
+
"toolId",
|
|
766
|
+
"name",
|
|
767
|
+
"providerId",
|
|
768
|
+
"requiredScopes",
|
|
769
|
+
"credentialIds",
|
|
770
|
+
"provenance",
|
|
771
|
+
"freshness",
|
|
772
|
+
], "tool record", ["providerId"]);
|
|
445
773
|
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION)
|
|
446
774
|
throw new InventoryError("Unsupported tool record schema.");
|
|
447
775
|
const toolKind = record.kind;
|
|
@@ -453,17 +781,35 @@ function validateToolRecord(value) {
|
|
|
453
781
|
toolId: identifier(record.toolId, "Tool id"),
|
|
454
782
|
name: text(record.name, "Tool name", 160),
|
|
455
783
|
kind: toolKind,
|
|
456
|
-
providerId: record.providerId === null
|
|
784
|
+
providerId: record.providerId === null
|
|
785
|
+
? null
|
|
786
|
+
: record.providerId === undefined
|
|
787
|
+
? null
|
|
788
|
+
: identifier(record.providerId, "Tool provider id"),
|
|
457
789
|
requiredScopes: scopes(record.requiredScopes, "Tool required scopes", INVENTORY_LIMITS.maxScopesPerRecord),
|
|
458
790
|
credentialIds: identifiers(record.credentialIds, "Tool credential ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
459
791
|
provenance: validateProvenance(record.provenance),
|
|
460
|
-
freshness: validateFreshness(record.freshness)
|
|
792
|
+
freshness: validateFreshness(record.freshness),
|
|
461
793
|
};
|
|
462
794
|
}
|
|
463
795
|
function validateIdentityRecord(value) {
|
|
464
796
|
const record = object(value, "Identity record is invalid.");
|
|
465
|
-
exactKeys(record, [
|
|
466
|
-
|
|
797
|
+
exactKeys(record, [
|
|
798
|
+
"schemaVersion",
|
|
799
|
+
"kind",
|
|
800
|
+
"tenantId",
|
|
801
|
+
"identityId",
|
|
802
|
+
"principalId",
|
|
803
|
+
"role",
|
|
804
|
+
"agentId",
|
|
805
|
+
"toolIds",
|
|
806
|
+
"environmentIds",
|
|
807
|
+
"scopes",
|
|
808
|
+
"provenance",
|
|
809
|
+
"freshness",
|
|
810
|
+
], "identity record", ["agentId"]);
|
|
811
|
+
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
|
|
812
|
+
record.kind !== "ghostapi.inventory-identity")
|
|
467
813
|
throw new InventoryError("Unsupported identity record schema.");
|
|
468
814
|
const role = record.role;
|
|
469
815
|
if (!IDENTITY_ROLES.includes(role))
|
|
@@ -475,20 +821,40 @@ function validateIdentityRecord(value) {
|
|
|
475
821
|
identityId: identifier(record.identityId, "Identity id"),
|
|
476
822
|
principalId: identifier(record.principalId, "Identity principal id"),
|
|
477
823
|
role: role,
|
|
478
|
-
agentId: record.agentId === null
|
|
824
|
+
agentId: record.agentId === null
|
|
825
|
+
? null
|
|
826
|
+
: record.agentId === undefined
|
|
827
|
+
? null
|
|
828
|
+
: identifier(record.agentId, "Identity agent id"),
|
|
479
829
|
toolIds: identifiers(record.toolIds, "Identity tool ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
480
830
|
environmentIds: identifiers(record.environmentIds, "Identity environment ids", 16),
|
|
481
831
|
scopes: scopes(record.scopes, "Identity scopes", INVENTORY_LIMITS.maxScopesPerRecord),
|
|
482
832
|
provenance: validateProvenance(record.provenance),
|
|
483
|
-
freshness: validateFreshness(record.freshness)
|
|
833
|
+
freshness: validateFreshness(record.freshness),
|
|
484
834
|
};
|
|
485
835
|
}
|
|
486
836
|
function validateProviderRecord(value) {
|
|
487
837
|
const record = object(value, "Provider record is invalid.");
|
|
488
|
-
exactKeys(record, [
|
|
489
|
-
|
|
838
|
+
exactKeys(record, [
|
|
839
|
+
"schemaVersion",
|
|
840
|
+
"kind",
|
|
841
|
+
"tenantId",
|
|
842
|
+
"providerId",
|
|
843
|
+
"name",
|
|
844
|
+
"providerType",
|
|
845
|
+
"ownerId",
|
|
846
|
+
"environmentIds",
|
|
847
|
+
"resourceIds",
|
|
848
|
+
"gatewayBound",
|
|
849
|
+
"killSwitchApplied",
|
|
850
|
+
"provenance",
|
|
851
|
+
"freshness",
|
|
852
|
+
], "provider record", ["ownerId"]);
|
|
853
|
+
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
|
|
854
|
+
record.kind !== "ghostapi.inventory-provider")
|
|
490
855
|
throw new InventoryError("Unsupported provider record schema.");
|
|
491
|
-
if (typeof record.gatewayBound !== "boolean" ||
|
|
856
|
+
if (typeof record.gatewayBound !== "boolean" ||
|
|
857
|
+
typeof record.killSwitchApplied !== "boolean")
|
|
492
858
|
throw new InventoryError("Provider record booleans are invalid.");
|
|
493
859
|
return {
|
|
494
860
|
schemaVersion: 1,
|
|
@@ -497,19 +863,39 @@ function validateProviderRecord(value) {
|
|
|
497
863
|
providerId: identifier(record.providerId, "Provider id"),
|
|
498
864
|
name: text(record.name, "Provider name", 160),
|
|
499
865
|
providerType: text(record.providerType, "Provider type", 120),
|
|
500
|
-
ownerId: record.ownerId === null
|
|
866
|
+
ownerId: record.ownerId === null
|
|
867
|
+
? null
|
|
868
|
+
: record.ownerId === undefined
|
|
869
|
+
? null
|
|
870
|
+
: identifier(record.ownerId, "Provider owner id"),
|
|
501
871
|
environmentIds: identifiers(record.environmentIds, "Provider environment ids", 16),
|
|
502
872
|
resourceIds: identifiers(record.resourceIds, "Provider resource ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
503
873
|
gatewayBound: record.gatewayBound,
|
|
504
874
|
killSwitchApplied: record.killSwitchApplied,
|
|
505
875
|
provenance: validateProvenance(record.provenance),
|
|
506
|
-
freshness: validateFreshness(record.freshness)
|
|
876
|
+
freshness: validateFreshness(record.freshness),
|
|
507
877
|
};
|
|
508
878
|
}
|
|
509
879
|
function validateResourceRecord(value) {
|
|
510
880
|
const record = object(value, "Resource record is invalid.");
|
|
511
|
-
exactKeys(record, [
|
|
512
|
-
|
|
881
|
+
exactKeys(record, [
|
|
882
|
+
"schemaVersion",
|
|
883
|
+
"kind",
|
|
884
|
+
"tenantId",
|
|
885
|
+
"resourceId",
|
|
886
|
+
"providerId",
|
|
887
|
+
"resourceType",
|
|
888
|
+
"name",
|
|
889
|
+
"environmentIds",
|
|
890
|
+
"sensitive",
|
|
891
|
+
"ownerId",
|
|
892
|
+
"businessPurpose",
|
|
893
|
+
"sideEffectIds",
|
|
894
|
+
"provenance",
|
|
895
|
+
"freshness",
|
|
896
|
+
], "resource record", ["ownerId", "businessPurpose"]);
|
|
897
|
+
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
|
|
898
|
+
record.kind !== "ghostapi.inventory-resource")
|
|
513
899
|
throw new InventoryError("Unsupported resource record schema.");
|
|
514
900
|
if (typeof record.sensitive !== "boolean")
|
|
515
901
|
throw new InventoryError("Resource record sensitive flag is invalid.");
|
|
@@ -523,16 +909,33 @@ function validateResourceRecord(value) {
|
|
|
523
909
|
name: text(record.name, "Resource name", 160),
|
|
524
910
|
environmentIds: identifiers(record.environmentIds, "Resource environment ids", 16),
|
|
525
911
|
sensitive: record.sensitive,
|
|
526
|
-
ownerId: record.ownerId === null
|
|
527
|
-
|
|
912
|
+
ownerId: record.ownerId === null
|
|
913
|
+
? null
|
|
914
|
+
: record.ownerId === undefined
|
|
915
|
+
? null
|
|
916
|
+
: identifier(record.ownerId, "Resource owner id"),
|
|
917
|
+
...(record.businessPurpose === undefined
|
|
918
|
+
? {}
|
|
919
|
+
: {
|
|
920
|
+
businessPurpose: text(record.businessPurpose, "Resource business purpose", 240),
|
|
921
|
+
}),
|
|
528
922
|
sideEffectIds: identifiers(record.sideEffectIds, "Resource side effect ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
529
923
|
provenance: validateProvenance(record.provenance),
|
|
530
|
-
freshness: validateFreshness(record.freshness)
|
|
924
|
+
freshness: validateFreshness(record.freshness),
|
|
531
925
|
};
|
|
532
926
|
}
|
|
533
927
|
function validateSideEffectRecord(value) {
|
|
534
928
|
const record = object(value, "Side effect record is invalid.");
|
|
535
|
-
exactKeys(record, [
|
|
929
|
+
exactKeys(record, [
|
|
930
|
+
"schemaVersion",
|
|
931
|
+
"tenantId",
|
|
932
|
+
"sideEffectId",
|
|
933
|
+
"resourceId",
|
|
934
|
+
"kind",
|
|
935
|
+
"severity",
|
|
936
|
+
"provenance",
|
|
937
|
+
"freshness",
|
|
938
|
+
], "side effect record");
|
|
536
939
|
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION)
|
|
537
940
|
throw new InventoryError("Unsupported side effect record schema.");
|
|
538
941
|
const kind = record.kind;
|
|
@@ -549,13 +952,32 @@ function validateSideEffectRecord(value) {
|
|
|
549
952
|
kind: kind,
|
|
550
953
|
severity: severity,
|
|
551
954
|
provenance: validateProvenance(record.provenance),
|
|
552
|
-
freshness: validateFreshness(record.freshness)
|
|
955
|
+
freshness: validateFreshness(record.freshness),
|
|
553
956
|
};
|
|
554
957
|
}
|
|
555
958
|
function validateCredentialRecord(value) {
|
|
556
959
|
const record = object(value, "Credential record is invalid.");
|
|
557
|
-
exactKeys(record, [
|
|
558
|
-
|
|
960
|
+
exactKeys(record, [
|
|
961
|
+
"schemaVersion",
|
|
962
|
+
"kind",
|
|
963
|
+
"tenantId",
|
|
964
|
+
"credentialId",
|
|
965
|
+
"name",
|
|
966
|
+
"providerId",
|
|
967
|
+
"ownerId",
|
|
968
|
+
"environmentIds",
|
|
969
|
+
"grantScopes",
|
|
970
|
+
"toolIds",
|
|
971
|
+
"status",
|
|
972
|
+
"issuedAt",
|
|
973
|
+
"lastUsedAt",
|
|
974
|
+
"rotatedAt",
|
|
975
|
+
"gatewayBound",
|
|
976
|
+
"provenance",
|
|
977
|
+
"freshness",
|
|
978
|
+
], "credential record", ["ownerId", "lastUsedAt", "rotatedAt"]);
|
|
979
|
+
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
|
|
980
|
+
record.kind !== "ghostapi.inventory-credential")
|
|
559
981
|
throw new InventoryError("Unsupported credential record schema.");
|
|
560
982
|
if (record.status !== "active" && record.status !== "revoked")
|
|
561
983
|
throw new InventoryError("Credential record status is invalid.");
|
|
@@ -568,23 +990,45 @@ function validateCredentialRecord(value) {
|
|
|
568
990
|
credentialId: identifier(record.credentialId, "Credential id"),
|
|
569
991
|
name: text(record.name, "Credential name", 160),
|
|
570
992
|
providerId: identifier(record.providerId, "Credential provider id"),
|
|
571
|
-
ownerId: record.ownerId === null
|
|
993
|
+
ownerId: record.ownerId === null
|
|
994
|
+
? null
|
|
995
|
+
: record.ownerId === undefined
|
|
996
|
+
? null
|
|
997
|
+
: identifier(record.ownerId, "Credential owner id"),
|
|
572
998
|
environmentIds: identifiers(record.environmentIds, "Credential environment ids", 16),
|
|
573
999
|
grantScopes: scopes(record.grantScopes, "Credential grant scopes", INVENTORY_LIMITS.maxScopesPerRecord),
|
|
574
1000
|
toolIds: identifiers(record.toolIds, "Credential tool ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
575
1001
|
status: record.status,
|
|
576
1002
|
issuedAt: timestamp(record.issuedAt, "Credential issued time"),
|
|
577
|
-
lastUsedAt: record.lastUsedAt === null
|
|
578
|
-
|
|
1003
|
+
lastUsedAt: record.lastUsedAt === null
|
|
1004
|
+
? null
|
|
1005
|
+
: record.lastUsedAt === undefined
|
|
1006
|
+
? null
|
|
1007
|
+
: timestamp(record.lastUsedAt, "Credential last used time"),
|
|
1008
|
+
...(record.rotatedAt === undefined
|
|
1009
|
+
? {}
|
|
1010
|
+
: { rotatedAt: timestamp(record.rotatedAt, "Credential rotated time") }),
|
|
579
1011
|
gatewayBound: record.gatewayBound,
|
|
580
1012
|
provenance: validateProvenance(record.provenance),
|
|
581
|
-
freshness: validateFreshness(record.freshness)
|
|
1013
|
+
freshness: validateFreshness(record.freshness),
|
|
582
1014
|
};
|
|
583
1015
|
}
|
|
584
1016
|
function validatePolicyRecord(value) {
|
|
585
1017
|
const record = object(value, "Policy record is invalid.");
|
|
586
|
-
exactKeys(record, [
|
|
587
|
-
|
|
1018
|
+
exactKeys(record, [
|
|
1019
|
+
"schemaVersion",
|
|
1020
|
+
"kind",
|
|
1021
|
+
"tenantId",
|
|
1022
|
+
"policyId",
|
|
1023
|
+
"name",
|
|
1024
|
+
"version",
|
|
1025
|
+
"hash",
|
|
1026
|
+
"environmentIds",
|
|
1027
|
+
"provenance",
|
|
1028
|
+
"freshness",
|
|
1029
|
+
], "policy record");
|
|
1030
|
+
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
|
|
1031
|
+
record.kind !== "ghostapi.inventory-policy")
|
|
588
1032
|
throw new InventoryError("Unsupported policy record schema.");
|
|
589
1033
|
return {
|
|
590
1034
|
schemaVersion: 1,
|
|
@@ -596,12 +1040,23 @@ function validatePolicyRecord(value) {
|
|
|
596
1040
|
hash: hash(record.hash, "Policy hash"),
|
|
597
1041
|
environmentIds: identifiers(record.environmentIds, "Policy environment ids", 16),
|
|
598
1042
|
provenance: validateProvenance(record.provenance),
|
|
599
|
-
freshness: validateFreshness(record.freshness)
|
|
1043
|
+
freshness: validateFreshness(record.freshness),
|
|
600
1044
|
};
|
|
601
1045
|
}
|
|
602
1046
|
function validateEdge(value) {
|
|
603
1047
|
const record = object(value, "Graph edge is invalid.");
|
|
604
|
-
exactKeys(record, [
|
|
1048
|
+
exactKeys(record, [
|
|
1049
|
+
"tenantId",
|
|
1050
|
+
"edgeId",
|
|
1051
|
+
"sourceKind",
|
|
1052
|
+
"sourceId",
|
|
1053
|
+
"targetKind",
|
|
1054
|
+
"targetId",
|
|
1055
|
+
"relation",
|
|
1056
|
+
"provenance",
|
|
1057
|
+
"firstSeenAt",
|
|
1058
|
+
"lastSeenAt",
|
|
1059
|
+
], "graph edge");
|
|
605
1060
|
if (!NODE_KINDS.includes(record.sourceKind))
|
|
606
1061
|
throw new InventoryError("Graph edge source kind is invalid.");
|
|
607
1062
|
if (!NODE_KINDS.includes(record.targetKind))
|
|
@@ -618,12 +1073,27 @@ function validateEdge(value) {
|
|
|
618
1073
|
relation: record.relation,
|
|
619
1074
|
provenance: validateProvenance(record.provenance),
|
|
620
1075
|
firstSeenAt: timestamp(record.firstSeenAt, "Edge first seen time"),
|
|
621
|
-
lastSeenAt: timestamp(record.lastSeenAt, "Edge last seen time")
|
|
1076
|
+
lastSeenAt: timestamp(record.lastSeenAt, "Edge last seen time"),
|
|
622
1077
|
};
|
|
623
1078
|
}
|
|
624
1079
|
function validateFindingRecord(value) {
|
|
625
1080
|
const record = object(value, "Finding record is invalid.");
|
|
626
|
-
exactKeys(record, [
|
|
1081
|
+
exactKeys(record, [
|
|
1082
|
+
"schemaVersion",
|
|
1083
|
+
"tenantId",
|
|
1084
|
+
"findingId",
|
|
1085
|
+
"kind",
|
|
1086
|
+
"severity",
|
|
1087
|
+
"targetKind",
|
|
1088
|
+
"targetId",
|
|
1089
|
+
"ruleId",
|
|
1090
|
+
"reason",
|
|
1091
|
+
"basis",
|
|
1092
|
+
"discoveredAt",
|
|
1093
|
+
"status",
|
|
1094
|
+
"resolvedAt",
|
|
1095
|
+
"remediationId",
|
|
1096
|
+
], "finding record", ["resolvedAt", "remediationId"]);
|
|
627
1097
|
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION)
|
|
628
1098
|
throw new InventoryError("Unsupported finding record schema.");
|
|
629
1099
|
if (!FINDING_KINDS.includes(record.kind))
|
|
@@ -634,7 +1104,15 @@ function validateFindingRecord(value) {
|
|
|
634
1104
|
throw new InventoryError("Finding basis is invalid.");
|
|
635
1105
|
if (record.status !== "open" && record.status !== "resolved")
|
|
636
1106
|
throw new InventoryError("Finding status is invalid.");
|
|
637
|
-
if (record.targetKind !== "agent" &&
|
|
1107
|
+
if (record.targetKind !== "agent" &&
|
|
1108
|
+
record.targetKind !== "identity" &&
|
|
1109
|
+
record.targetKind !== "tool" &&
|
|
1110
|
+
record.targetKind !== "provider" &&
|
|
1111
|
+
record.targetKind !== "resource" &&
|
|
1112
|
+
record.targetKind !== "side_effect" &&
|
|
1113
|
+
record.targetKind !== "credential" &&
|
|
1114
|
+
record.targetKind !== "environment" &&
|
|
1115
|
+
record.targetKind !== "policy")
|
|
638
1116
|
throw new InventoryError("Finding target kind is invalid.");
|
|
639
1117
|
return {
|
|
640
1118
|
schemaVersion: 1,
|
|
@@ -649,22 +1127,52 @@ function validateFindingRecord(value) {
|
|
|
649
1127
|
basis: record.basis,
|
|
650
1128
|
discoveredAt: timestamp(record.discoveredAt, "Finding discovery time"),
|
|
651
1129
|
status: record.status,
|
|
652
|
-
...(record.resolvedAt === undefined
|
|
653
|
-
|
|
1130
|
+
...(record.resolvedAt === undefined
|
|
1131
|
+
? {}
|
|
1132
|
+
: { resolvedAt: timestamp(record.resolvedAt, "Finding resolved time") }),
|
|
1133
|
+
...(record.remediationId === undefined
|
|
1134
|
+
? {}
|
|
1135
|
+
: {
|
|
1136
|
+
remediationId: identifier(record.remediationId, "Finding remediation id"),
|
|
1137
|
+
}),
|
|
654
1138
|
};
|
|
655
1139
|
}
|
|
656
1140
|
function validateRemediationRecord(value) {
|
|
657
1141
|
const record = object(value, "Remediation record is invalid.");
|
|
658
|
-
exactKeys(record, [
|
|
1142
|
+
exactKeys(record, [
|
|
1143
|
+
"schemaVersion",
|
|
1144
|
+
"tenantId",
|
|
1145
|
+
"remediationId",
|
|
1146
|
+
"findingId",
|
|
1147
|
+
"kind",
|
|
1148
|
+
"targetKind",
|
|
1149
|
+
"targetId",
|
|
1150
|
+
"rationale",
|
|
1151
|
+
"proposedBy",
|
|
1152
|
+
"createdAt",
|
|
1153
|
+
"status",
|
|
1154
|
+
"appliedAt",
|
|
1155
|
+
"result",
|
|
1156
|
+
], "remediation record", ["appliedAt", "result"]);
|
|
659
1157
|
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION)
|
|
660
1158
|
throw new InventoryError("Unsupported remediation record schema.");
|
|
661
1159
|
if (!REMEDIATION_KINDS.includes(record.kind))
|
|
662
1160
|
throw new InventoryError("Remediation kind is invalid.");
|
|
663
1161
|
if (!REMEDIATION_STATUSES.includes(record.status))
|
|
664
1162
|
throw new InventoryError("Remediation status is invalid.");
|
|
665
|
-
if (record.targetKind !== "agent" &&
|
|
1163
|
+
if (record.targetKind !== "agent" &&
|
|
1164
|
+
record.targetKind !== "identity" &&
|
|
1165
|
+
record.targetKind !== "tool" &&
|
|
1166
|
+
record.targetKind !== "provider" &&
|
|
1167
|
+
record.targetKind !== "resource" &&
|
|
1168
|
+
record.targetKind !== "side_effect" &&
|
|
1169
|
+
record.targetKind !== "credential" &&
|
|
1170
|
+
record.targetKind !== "environment" &&
|
|
1171
|
+
record.targetKind !== "policy")
|
|
666
1172
|
throw new InventoryError("Remediation target kind is invalid.");
|
|
667
|
-
const result = record.result === undefined
|
|
1173
|
+
const result = record.result === undefined
|
|
1174
|
+
? undefined
|
|
1175
|
+
: validateRemediationResult(record.result);
|
|
668
1176
|
return {
|
|
669
1177
|
schemaVersion: 1,
|
|
670
1178
|
tenantId: identifier(record.tenantId, "Remediation tenant id"),
|
|
@@ -677,8 +1185,10 @@ function validateRemediationRecord(value) {
|
|
|
677
1185
|
proposedBy: identifier(record.proposedBy, "Remediation proposer"),
|
|
678
1186
|
createdAt: timestamp(record.createdAt, "Remediation creation time"),
|
|
679
1187
|
status: record.status,
|
|
680
|
-
...(record.appliedAt === undefined
|
|
681
|
-
|
|
1188
|
+
...(record.appliedAt === undefined
|
|
1189
|
+
? {}
|
|
1190
|
+
: { appliedAt: timestamp(record.appliedAt, "Remediation applied time") }),
|
|
1191
|
+
...(result === undefined ? {} : { result }),
|
|
682
1192
|
};
|
|
683
1193
|
}
|
|
684
1194
|
function validateRemediationResult(value) {
|
|
@@ -686,9 +1196,19 @@ function validateRemediationResult(value) {
|
|
|
686
1196
|
exactKeys(result, ["description", "reducedScopes", "evalScenarioId", "ownerId"], "remediation result", ["reducedScopes", "evalScenarioId", "ownerId"]);
|
|
687
1197
|
return {
|
|
688
1198
|
description: text(result.description, "Remediation result description", 400),
|
|
689
|
-
...(result.reducedScopes === undefined
|
|
690
|
-
|
|
691
|
-
|
|
1199
|
+
...(result.reducedScopes === undefined
|
|
1200
|
+
? {}
|
|
1201
|
+
: {
|
|
1202
|
+
reducedScopes: scopes(result.reducedScopes, "Remediation reduced scopes", INVENTORY_LIMITS.maxScopesPerRecord),
|
|
1203
|
+
}),
|
|
1204
|
+
...(result.evalScenarioId === undefined
|
|
1205
|
+
? {}
|
|
1206
|
+
: {
|
|
1207
|
+
evalScenarioId: identifier(result.evalScenarioId, "Remediation eval scenario id"),
|
|
1208
|
+
}),
|
|
1209
|
+
...(result.ownerId === undefined
|
|
1210
|
+
? {}
|
|
1211
|
+
: { ownerId: identifier(result.ownerId, "Remediation owner id") }),
|
|
692
1212
|
};
|
|
693
1213
|
}
|
|
694
1214
|
function validateProvenance(value) {
|
|
@@ -702,7 +1222,7 @@ function validateProvenance(value) {
|
|
|
702
1222
|
sourceType: sourceType,
|
|
703
1223
|
sourceName: text(provenance.sourceName, "Provenance source name", 120),
|
|
704
1224
|
importedAt: timestamp(provenance.importedAt, "Provenance import time"),
|
|
705
|
-
importedBy: identifier(provenance.importedBy, "Provenance importer")
|
|
1225
|
+
importedBy: identifier(provenance.importedBy, "Provenance importer"),
|
|
706
1226
|
};
|
|
707
1227
|
}
|
|
708
1228
|
function validateFreshness(value) {
|
|
@@ -710,11 +1230,17 @@ function validateFreshness(value) {
|
|
|
710
1230
|
exactKeys(freshness, ["firstSeenAt", "lastSeenAt"], "record freshness");
|
|
711
1231
|
return {
|
|
712
1232
|
firstSeenAt: timestamp(freshness.firstSeenAt, "Record first seen time"),
|
|
713
|
-
lastSeenAt: timestamp(freshness.lastSeenAt, "Record last seen time")
|
|
1233
|
+
lastSeenAt: timestamp(freshness.lastSeenAt, "Record last seen time"),
|
|
714
1234
|
};
|
|
715
1235
|
}
|
|
716
1236
|
export function buildRecordProvenance(source, importedAt, importedBy) {
|
|
717
|
-
return {
|
|
1237
|
+
return {
|
|
1238
|
+
sourceId: source.sourceId,
|
|
1239
|
+
sourceType: source.sourceType,
|
|
1240
|
+
sourceName: source.sourceName,
|
|
1241
|
+
importedAt,
|
|
1242
|
+
importedBy,
|
|
1243
|
+
};
|
|
718
1244
|
}
|
|
719
1245
|
export function buildRecordFreshness(now) {
|
|
720
1246
|
return { firstSeenAt: now, lastSeenAt: now };
|
|
@@ -741,7 +1267,8 @@ function array(value, label, max) {
|
|
|
741
1267
|
}
|
|
742
1268
|
function exactKeys(value, keys, label, optional = []) {
|
|
743
1269
|
for (const key of Object.keys(value)) {
|
|
744
|
-
if (!keys.includes(key) ||
|
|
1270
|
+
if (!keys.includes(key) ||
|
|
1271
|
+
(value[key] === undefined && !optional.includes(key)))
|
|
745
1272
|
throw new InventoryError(`${label} contains unsupported field: ${key}`);
|
|
746
1273
|
}
|
|
747
1274
|
}
|
|
@@ -750,7 +1277,9 @@ function unique(values, label) {
|
|
|
750
1277
|
throw new InventoryError(`${label} must be unique.`);
|
|
751
1278
|
}
|
|
752
1279
|
function identifier(value, label) {
|
|
753
|
-
if (typeof value !== "string" ||
|
|
1280
|
+
if (typeof value !== "string" ||
|
|
1281
|
+
!IDENTIFIER.test(value) ||
|
|
1282
|
+
sanitizeSecretString(value) !== value)
|
|
754
1283
|
throw new InventoryError(`${label} must be a safe identifier.`);
|
|
755
1284
|
return value;
|
|
756
1285
|
}
|
|
@@ -759,18 +1288,26 @@ function identifiers(value, label, max) {
|
|
|
759
1288
|
}
|
|
760
1289
|
function scopes(value, label, max) {
|
|
761
1290
|
return array(value, label, max).map((entry) => {
|
|
762
|
-
if (typeof entry !== "string" ||
|
|
1291
|
+
if (typeof entry !== "string" ||
|
|
1292
|
+
!SCOPE_IDENTIFIER.test(entry) ||
|
|
1293
|
+
sanitizeSecretString(entry) !== entry)
|
|
763
1294
|
throw new InventoryError(`${label} must be safe scope identifiers.`);
|
|
764
1295
|
return entry;
|
|
765
1296
|
});
|
|
766
1297
|
}
|
|
767
1298
|
function text(value, label, max) {
|
|
768
|
-
if (typeof value !== "string" ||
|
|
1299
|
+
if (typeof value !== "string" ||
|
|
1300
|
+
value.trim() === "" ||
|
|
1301
|
+
value.length > max ||
|
|
1302
|
+
/[\u0000-\u001f]/.test(value) ||
|
|
1303
|
+
sanitizeSecretString(value) !== value)
|
|
769
1304
|
throw new InventoryError(`${label} is invalid.`);
|
|
770
1305
|
return value.trim();
|
|
771
1306
|
}
|
|
772
1307
|
function timestamp(value, label) {
|
|
773
|
-
if (typeof value !== "string" ||
|
|
1308
|
+
if (typeof value !== "string" ||
|
|
1309
|
+
!TIMESTAMP.test(value) ||
|
|
1310
|
+
!Number.isFinite(Date.parse(value)))
|
|
774
1311
|
throw new InventoryError(`${label} must be an ISO UTC timestamp.`);
|
|
775
1312
|
return value;
|
|
776
1313
|
}
|
|
@@ -780,7 +1317,10 @@ function hash(value, label) {
|
|
|
780
1317
|
return value;
|
|
781
1318
|
}
|
|
782
1319
|
function nonNegative(value, label, max) {
|
|
783
|
-
if (typeof value !== "number" ||
|
|
1320
|
+
if (typeof value !== "number" ||
|
|
1321
|
+
!Number.isInteger(value) ||
|
|
1322
|
+
value < 0 ||
|
|
1323
|
+
value > max)
|
|
784
1324
|
throw new InventoryError(`${label} is invalid.`);
|
|
785
1325
|
return value;
|
|
786
1326
|
}
|