@yiaany/ghostapi 0.1.6 → 0.1.8
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 +48 -2
- package/CONTRIBUTING.md +119 -117
- package/LICENSE +21 -21
- package/README.md +208 -185
- package/SECURITY.md +53 -37
- package/dist/actions/gateway.d.ts +179 -0
- package/dist/actions/gateway.js +514 -0
- package/dist/actions/gateway.js.map +1 -0
- package/dist/actions/index.d.ts +2 -0
- package/dist/actions/index.js +2 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/ai/aiClient.js +4 -4
- package/dist/ai/aiClient.js.map +1 -1
- package/dist/ai/aiGenerator.d.ts +2 -2
- package/dist/ai/aiGenerator.js +21 -72
- package/dist/ai/aiGenerator.js.map +1 -1
- package/dist/ai/prompts.js +51 -51
- package/dist/ai/prompts.js.map +1 -1
- package/dist/approvals/approvalInbox.d.ts +150 -0
- package/dist/approvals/approvalInbox.js +412 -0
- package/dist/approvals/approvalInbox.js.map +1 -0
- package/dist/approvals/index.d.ts +2 -0
- package/dist/approvals/index.js +2 -0
- package/dist/approvals/index.js.map +1 -0
- package/dist/behavior/behaviorStore.d.ts +2 -0
- package/dist/behavior/behaviorStore.js +50 -46
- package/dist/behavior/behaviorStore.js.map +1 -1
- package/dist/cache/index.d.ts +2 -0
- package/dist/cache/index.js +58 -14
- package/dist/cache/index.js.map +1 -1
- package/dist/cli/index.js +438 -58
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/openUrl.d.ts +10 -0
- package/dist/cli/openUrl.js +31 -0
- package/dist/cli/openUrl.js.map +1 -0
- package/dist/cli/parser.d.ts +155 -0
- package/dist/cli/parser.js +504 -2
- package/dist/cli/parser.js.map +1 -1
- package/dist/config/dataPaths.d.ts +31 -0
- package/dist/config/dataPaths.js +45 -0
- package/dist/config/dataPaths.js.map +1 -0
- package/dist/config/localConfig.d.ts +1 -2
- package/dist/config/localConfig.js +27 -21
- package/dist/config/localConfig.js.map +1 -1
- package/dist/config/serverConfig.d.ts +2 -0
- package/dist/config/serverConfig.js +8 -1
- package/dist/config/serverConfig.js.map +1 -1
- package/dist/contracts/contract.d.ts +76 -0
- package/dist/contracts/contract.js +603 -0
- package/dist/contracts/contract.js.map +1 -0
- package/dist/contracts/index.d.ts +2 -0
- package/dist/contracts/index.js +2 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/credentials/credentialBroker.d.ts +207 -0
- package/dist/credentials/credentialBroker.js +588 -0
- package/dist/credentials/credentialBroker.js.map +1 -0
- package/dist/credentials/index.d.ts +2 -0
- package/dist/credentials/index.js +2 -0
- package/dist/credentials/index.js.map +1 -0
- package/dist/dashboard/app.js +685 -615
- package/dist/dashboard/index.html +13 -7
- package/dist/dashboard/styles.css +349 -349
- package/dist/egress/capabilities.d.ts +32 -0
- package/dist/egress/capabilities.js +165 -0
- package/dist/egress/capabilities.js.map +1 -0
- package/dist/egress/linuxBootstrap.d.ts +1 -0
- package/dist/egress/linuxBootstrap.js +121 -0
- package/dist/egress/linuxBootstrap.js.map +1 -0
- package/dist/egress/run.d.ts +18 -0
- package/dist/egress/run.js +305 -0
- package/dist/egress/run.js.map +1 -0
- package/dist/errors/errorEngine.d.ts +2 -2
- package/dist/errors/errorEngine.js +3 -40
- package/dist/errors/errorEngine.js.map +1 -1
- package/dist/evals/evals.d.ts +126 -0
- package/dist/evals/evals.js +466 -0
- package/dist/evals/evals.js.map +1 -0
- package/dist/evals/index.d.ts +2 -0
- package/dist/evals/index.js +2 -0
- package/dist/evals/index.js.map +1 -0
- package/dist/evidence/index.d.ts +2 -0
- package/dist/evidence/index.js +2 -0
- package/dist/evidence/index.js.map +1 -0
- package/dist/evidence/report.d.ts +143 -0
- package/dist/evidence/report.js +556 -0
- package/dist/evidence/report.js.map +1 -0
- package/dist/fault/faultLab.d.ts +5 -4
- package/dist/fault/faultLab.js +66 -24
- package/dist/fault/faultLab.js.map +1 -1
- package/dist/index.d.ts +46 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -1
- package/dist/inventory/analysis.d.ts +12 -0
- package/dist/inventory/analysis.js +457 -0
- package/dist/inventory/analysis.js.map +1 -0
- package/dist/inventory/index.d.ts +5 -0
- package/dist/inventory/index.js +4 -0
- package/dist/inventory/index.js.map +1 -0
- package/dist/inventory/inventoryController.d.ts +53 -0
- package/dist/inventory/inventoryController.js +1044 -0
- package/dist/inventory/inventoryController.js.map +1 -0
- package/dist/inventory/types.d.ts +579 -0
- package/dist/inventory/types.js +787 -0
- package/dist/inventory/types.js.map +1 -0
- package/dist/landing/assets/index-CxWsopzP.css +1 -0
- package/dist/landing/assets/index-DZlpyhqd.js +55 -0
- package/dist/landing/assets/index-Dk-Xezs3.js +55 -0
- package/dist/landing/index.html +15 -15
- package/dist/ledger/actionLedger.d.ts +125 -0
- package/dist/ledger/actionLedger.js +594 -0
- package/dist/ledger/actionLedger.js.map +1 -0
- package/dist/ledger/index.d.ts +2 -0
- package/dist/ledger/index.js +2 -0
- package/dist/ledger/index.js.map +1 -0
- package/dist/mcp/server.js +4 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/policy/index.d.ts +3 -0
- package/dist/policy/index.js +2 -0
- package/dist/policy/index.js.map +1 -0
- package/dist/policy/policy.d.ts +14 -0
- package/dist/policy/policy.js +282 -0
- package/dist/policy/policy.js.map +1 -0
- package/dist/policy/types.d.ts +52 -0
- package/dist/policy/types.js +2 -0
- package/dist/policy/types.js.map +1 -0
- package/dist/productTelemetry/index.d.ts +16 -0
- package/dist/productTelemetry/index.js +142 -0
- package/dist/productTelemetry/index.js.map +1 -0
- package/dist/providers/conformance.d.ts +7 -0
- package/dist/providers/conformance.js +48 -0
- package/dist/providers/conformance.js.map +1 -0
- package/dist/providers/index.d.ts +4 -2
- package/dist/providers/index.js +3 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/packs/resendPack.d.ts +2 -0
- package/dist/providers/packs/resendPack.js +147 -0
- package/dist/providers/packs/resendPack.js.map +1 -0
- package/dist/providers/packs/stripePack.d.ts +2 -0
- package/dist/providers/packs/stripePack.js +852 -0
- package/dist/providers/packs/stripePack.js.map +1 -0
- package/dist/providers/registry.d.ts +7 -3
- package/dist/providers/registry.js +55 -4
- package/dist/providers/registry.js.map +1 -1
- package/dist/providers/resend.js +2 -11
- package/dist/providers/resend.js.map +1 -1
- package/dist/providers/runtime.d.ts +8 -0
- package/dist/providers/runtime.js +48 -0
- package/dist/providers/runtime.js.map +1 -0
- package/dist/providers/stripeWebhook.d.ts +13 -0
- package/dist/providers/stripeWebhook.js +50 -0
- package/dist/providers/stripeWebhook.js.map +1 -0
- package/dist/providers/types.d.ts +153 -0
- package/dist/proxy/cacheKey.js +3 -3
- package/dist/proxy/cacheKey.js.map +1 -1
- package/dist/proxy/providerDetector.d.ts +2 -7
- package/dist/proxy/providerDetector.js +4 -4
- package/dist/proxy/providerDetector.js.map +1 -1
- package/dist/proxy/proxyHandler.js +116 -12
- package/dist/proxy/proxyHandler.js.map +1 -1
- package/dist/proxy/requestNormalizer.d.ts +0 -1
- package/dist/proxy/requestNormalizer.js +2 -5
- package/dist/proxy/requestNormalizer.js.map +1 -1
- package/dist/reliability/costGovernance.d.ts +175 -0
- package/dist/reliability/costGovernance.js +515 -0
- package/dist/reliability/costGovernance.js.map +1 -0
- package/dist/reliability/index.d.ts +8 -0
- package/dist/reliability/index.js +5 -0
- package/dist/reliability/index.js.map +1 -0
- package/dist/reliability/reconciliation.d.ts +157 -0
- package/dist/reliability/reconciliation.js +522 -0
- package/dist/reliability/reconciliation.js.map +1 -0
- package/dist/reliability/runtimeHealth.d.ts +60 -0
- package/dist/reliability/runtimeHealth.js +356 -0
- package/dist/reliability/runtimeHealth.js.map +1 -0
- package/dist/reliability/slo.d.ts +131 -0
- package/dist/reliability/slo.js +375 -0
- package/dist/reliability/slo.js.map +1 -0
- package/dist/report/safetyReport.js +7 -3
- package/dist/report/safetyReport.js.map +1 -1
- package/dist/safety/index.d.ts +2 -0
- package/dist/safety/index.js +2 -0
- package/dist/safety/index.js.map +1 -0
- package/dist/safety/safetyController.d.ts +187 -0
- package/dist/safety/safetyController.js +477 -0
- package/dist/safety/safetyController.js.map +1 -0
- package/dist/scenarios/scenarioBundle.d.ts +75 -0
- package/dist/scenarios/scenarioBundle.js +750 -0
- package/dist/scenarios/scenarioBundle.js.map +1 -0
- package/dist/scenarios/scenarioStore.d.ts +1 -0
- package/dist/scenarios/scenarioStore.js +47 -32
- package/dist/scenarios/scenarioStore.js.map +1 -1
- package/dist/security/headerSanitizer.d.ts +2 -0
- package/dist/security/headerSanitizer.js +36 -1
- package/dist/security/headerSanitizer.js.map +1 -1
- package/dist/security/secrets.js +24 -4
- package/dist/security/secrets.js.map +1 -1
- package/dist/server/accessControl.d.ts +5 -0
- package/dist/server/accessControl.js +100 -0
- package/dist/server/accessControl.js.map +1 -0
- package/dist/server/createServer.d.ts +0 -5
- package/dist/server/createServer.js +9 -29
- package/dist/server/createServer.js.map +1 -1
- package/dist/server/eventsStore.d.ts +6 -1
- package/dist/server/eventsStore.js +83 -15
- package/dist/server/eventsStore.js.map +1 -1
- package/dist/server/routes.js +36 -10
- package/dist/server/routes.js.map +1 -1
- package/dist/server/sse.d.ts +2 -1
- package/dist/server/sse.js +15 -3
- package/dist/server/sse.js.map +1 -1
- package/dist/setup/setupGenerator.js +53 -6
- package/dist/setup/setupGenerator.js.map +1 -1
- package/dist/state/stateStore.d.ts +5 -0
- package/dist/state/stateStore.js +48 -62
- package/dist/state/stateStore.js.map +1 -1
- package/dist/storage/fileStore.d.ts +10 -0
- package/dist/storage/fileStore.js +131 -0
- package/dist/storage/fileStore.js.map +1 -0
- package/dist/teamControl/controlPlane.d.ts +255 -0
- package/dist/teamControl/controlPlane.js +918 -0
- package/dist/teamControl/controlPlane.js.map +1 -0
- package/dist/teamControl/deployment.d.ts +33 -0
- package/dist/teamControl/deployment.js +67 -0
- package/dist/teamControl/deployment.js.map +1 -0
- package/dist/teamControl/index.d.ts +4 -0
- package/dist/teamControl/index.js +3 -0
- package/dist/teamControl/index.js.map +1 -0
- package/dist/tests/testGenerator.js +4 -1
- package/dist/tests/testGenerator.js.map +1 -1
- package/dist/trust/index.d.ts +2 -0
- package/dist/trust/index.js +2 -0
- package/dist/trust/index.js.map +1 -0
- package/dist/trust/trustLadder.d.ts +182 -0
- package/dist/trust/trustLadder.js +560 -0
- package/dist/trust/trustLadder.js.map +1 -0
- package/dist/worlds/index.d.ts +2 -0
- package/dist/worlds/index.js +2 -0
- package/dist/worlds/index.js.map +1 -0
- package/dist/worlds/worlds.d.ts +160 -0
- package/dist/worlds/worlds.js +491 -0
- package/dist/worlds/worlds.js.map +1 -0
- package/docs/assets/dashboard.png +0 -0
- package/docs/ci.md +49 -0
- package/docs/commercial/README.md +29 -0
- package/docs/commercial/data-inventory.md +36 -0
- package/docs/commercial/manual-invoicing.md +44 -0
- package/docs/commercial/metrics.md +51 -0
- package/docs/commercial/pricing.md +103 -0
- package/docs/commercial/privacy-policy-draft.md +41 -0
- package/docs/commercial/terms-topics-for-counsel.md +51 -0
- package/docs/design-partners/README.md +52 -0
- package/docs/design-partners/demo-narratives.md +33 -0
- package/docs/design-partners/design-partner-offer.md +39 -0
- package/docs/design-partners/discovery-questions.md +28 -0
- package/docs/design-partners/feedback-capture-template.md +48 -0
- package/docs/design-partners/icp-one-pager.md +43 -0
- package/docs/design-partners/onboarding-checklist.md +33 -0
- package/docs/design-partners/pilot-success-criteria.md +27 -0
- package/docs/design-partners/pricing-interview-script.md +18 -0
- package/docs/design-partners/telemetry-plan.md +48 -0
- package/docs/development/baseline.md +114 -0
- package/docs/development/onboarding-smoke.md +37 -0
- package/docs/development/verification-0.1.8.md +25 -0
- package/docs/enterprise-product-roadmap-ru.md +1879 -0
- package/docs/fundraising/README.md +87 -0
- package/docs/fundraising/data-room-checklist.md +72 -0
- package/docs/fundraising/demo-script.md +52 -0
- package/docs/fundraising/design-partners-50.md +74 -0
- package/docs/fundraising/launch-posts.md +37 -0
- package/docs/fundraising/metrics-and-evidence.md +30 -0
- package/docs/fundraising/roadmap-12-month.md +25 -0
- package/docs/fundraising/technical-due-diligence-index.md +37 -0
- package/docs/fundraising/yc-application.md +25 -0
- package/docs/github-actions.md +84 -0
- package/docs/hosted-pilot.md +111 -0
- package/docs/mcp.md +52 -50
- package/docs/operations/disaster-recovery-runbook.md +81 -0
- package/docs/operations/kill-switch-runbook.md +32 -0
- package/docs/policy.md +61 -0
- package/docs/providers/authoring-packs.md +175 -0
- package/docs/providers/stripe-core-pack.md +82 -0
- package/docs/release-checklist.md +83 -71
- package/docs/release-migration-and-rollback.md +63 -0
- package/docs/release-readiness.md +39 -0
- package/docs/security/action-gateway-threat-model.md +34 -0
- package/docs/security/action-ledger-incident-replay-threat-model.md +42 -0
- package/docs/security/approval-inbox-threat-model.md +35 -0
- package/docs/security/credential-broker-threat-model.md +52 -0
- package/docs/security/egress-threat-model.md +126 -0
- package/docs/security/inventory-threat-model.md +104 -0
- package/docs/security/kill-switch-budgets-threat-model.md +37 -0
- package/docs/security/reliability-threat-model.md +111 -0
- package/docs/security/trust-ladder-threat-model.md +33 -0
- package/docs/team-control-plane.md +72 -0
- package/docs/usage.md +571 -57
- package/examples/README.md +10 -0
- package/examples/agent-instructions/README.md +39 -39
- package/examples/ci-smoke/README.md +21 -0
- package/examples/ci-smoke/ghostapi.policy.yaml +22 -0
- package/examples/ci-smoke/package.json +9 -0
- package/examples/ci-smoke/production-egress.mjs +2 -0
- package/examples/ci-smoke/safe.mjs +17 -0
- package/examples/evals/README.md +24 -0
- package/examples/evals/retry-after.eval.json +52 -0
- package/examples/generic-rest/README.md +27 -27
- package/examples/github-fetch/README.md +35 -35
- package/examples/openai-streaming/README.md +16 -0
- package/examples/openai-streaming/streaming-tool-call.mjs +31 -0
- package/examples/policy/ghostapi.policy.yaml +27 -0
- package/examples/record-replay/README.md +19 -0
- package/examples/record-replay/replay-requests.json +22 -0
- package/examples/record-replay/stripe-checkout.har.json +30 -0
- package/examples/resend-node/README.md +22 -22
- package/examples/stripe-node/README.md +35 -29
- package/examples/stripe-node/checkout-flow.mjs +61 -0
- package/examples/twilio-node/README.md +21 -21
- package/examples/worlds/README.md +19 -0
- package/examples/worlds/subscription-recovery.mjs +12 -0
- package/package.json +26 -11
- package/dist/providers/stripe.d.ts +0 -2
- package/dist/providers/stripe.js +0 -15
- package/dist/providers/stripe.js.map +0 -1
- package/docs/assets/ghostapi-avatar.png +0 -0
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { sanitizeSecretString } from "../security/secrets.js";
|
|
3
|
+
export const INVENTORY_SCHEMA_VERSION = 1;
|
|
4
|
+
export const INVENTORY_KIND = "ghostapi.inventory";
|
|
5
|
+
export const IMPORT_SOURCE_TYPES = ["config", "ci", "gateway", "cloud"];
|
|
6
|
+
export const TOOL_KINDS = ["mcp_server", "sdk_client", "http_client"];
|
|
7
|
+
export const IDENTITY_ROLES = ["service_account", "user", "workload"];
|
|
8
|
+
export const SIDE_EFFECT_KINDS = ["read", "create", "update", "communicate", "money_movement", "delete", "permission_change", "deployment"];
|
|
9
|
+
export const IMPACT_SEVERITIES = ["low", "medium", "high", "critical"];
|
|
10
|
+
export const EDGE_RELATIONS = ["owns", "uses", "connects", "exposes", "permits", "requires"];
|
|
11
|
+
export const NODE_KINDS = ["agent", "identity", "tool", "provider", "resource", "side_effect", "credential"];
|
|
12
|
+
export const FINDING_KINDS = [
|
|
13
|
+
"orphaned_agents",
|
|
14
|
+
"stale_unused_credentials",
|
|
15
|
+
"excessive_permissions",
|
|
16
|
+
"unowned_production_integrations",
|
|
17
|
+
"agents_outside_gateway",
|
|
18
|
+
"missing_kill_switch",
|
|
19
|
+
"missing_evidence",
|
|
20
|
+
"policy_drift"
|
|
21
|
+
];
|
|
22
|
+
export const FINDING_BASIS = ["evidence", "heuristic"];
|
|
23
|
+
export const FINDING_SEVERITIES = ["critical", "high", "medium", "low"];
|
|
24
|
+
export const REMEDIATION_KINDS = ["assign_owner", "reduce_scope", "revoke", "onboard_through_gateway", "create_eval"];
|
|
25
|
+
export const REMEDIATION_STATUSES = ["proposed", "applied", "rejected", "expired"];
|
|
26
|
+
export const FRESHNESS_DEFAULT_DAYS = { edgeStale: 90, credentialStale: 90, evidenceFresh: 30 };
|
|
27
|
+
const IDENTIFIER = /^[a-z0-9][a-z0-9._-]{0,127}$/;
|
|
28
|
+
const SCOPE_IDENTIFIER = /^[a-z0-9][a-z0-9._:\-]{0,127}$/;
|
|
29
|
+
const HASH = /^[a-f0-9]{64}$/;
|
|
30
|
+
const TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
31
|
+
export const INVENTORY_LIMITS = {
|
|
32
|
+
maxStoreBytes: 8 * 1024 * 1024,
|
|
33
|
+
maxSources: 64,
|
|
34
|
+
maxImports: 256,
|
|
35
|
+
maxAgents: 500,
|
|
36
|
+
maxTools: 500,
|
|
37
|
+
maxIdentities: 500,
|
|
38
|
+
maxProviders: 200,
|
|
39
|
+
maxResources: 1000,
|
|
40
|
+
maxSideEffects: 2000,
|
|
41
|
+
maxCredentials: 500,
|
|
42
|
+
maxPolicies: 100,
|
|
43
|
+
maxEdges: 6000,
|
|
44
|
+
maxFindings: 3000,
|
|
45
|
+
maxRemediations: 1000,
|
|
46
|
+
maxReferencesPerRecord: 64,
|
|
47
|
+
maxScopesPerRecord: 32,
|
|
48
|
+
maxCounters: 3
|
|
49
|
+
};
|
|
50
|
+
export class InventoryError extends Error {
|
|
51
|
+
constructor(message) {
|
|
52
|
+
super(message);
|
|
53
|
+
this.name = "InventoryError";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export function emptyInventoryState() {
|
|
57
|
+
return {
|
|
58
|
+
schemaVersion: 1,
|
|
59
|
+
kind: "ghostapi.inventory",
|
|
60
|
+
sources: [],
|
|
61
|
+
importRuns: [],
|
|
62
|
+
agents: [],
|
|
63
|
+
tools: [],
|
|
64
|
+
identities: [],
|
|
65
|
+
providers: [],
|
|
66
|
+
resources: [],
|
|
67
|
+
sideEffects: [],
|
|
68
|
+
credentials: [],
|
|
69
|
+
policies: [],
|
|
70
|
+
edges: [],
|
|
71
|
+
findings: [],
|
|
72
|
+
remediations: []
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function sha256Digest(value) {
|
|
76
|
+
return createHash("sha256").update(value).digest("hex");
|
|
77
|
+
}
|
|
78
|
+
export function canonicalPayloadDigest(payload) {
|
|
79
|
+
return sha256Digest(JSON.stringify(payload, sortedKeysReplacer));
|
|
80
|
+
}
|
|
81
|
+
export function sortedKeysReplacer(_key, value) {
|
|
82
|
+
if (Array.isArray(value))
|
|
83
|
+
return value;
|
|
84
|
+
if (value !== null && typeof value === "object") {
|
|
85
|
+
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)));
|
|
86
|
+
}
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
export function validateImportPayload(value) {
|
|
90
|
+
const payload = object(value, "Inventory import must be an object.");
|
|
91
|
+
exactKeys(payload, ["schemaVersion", "kind", "source", "expectedPolicyHashes", "counters", "agents", "tools", "identities", "providers", "resources", "sideEffects", "credentials", "policies"], "inventory import", [
|
|
92
|
+
"expectedPolicyHashes", "counters", "agents", "tools", "identities", "providers", "resources", "sideEffects", "credentials", "policies"
|
|
93
|
+
]);
|
|
94
|
+
if (payload.schemaVersion !== INVENTORY_SCHEMA_VERSION || payload.kind !== "ghostapi.inventory-import")
|
|
95
|
+
throw new InventoryError("Unsupported inventory import schema.");
|
|
96
|
+
const source = validateImportSource(payload.source);
|
|
97
|
+
if (source.sourceType === "config" && payload.counters !== undefined)
|
|
98
|
+
throw new InventoryError("Counters are only allowed for ci, gateway, or cloud imports.");
|
|
99
|
+
const expectedPolicyHashes = payload.expectedPolicyHashes === undefined ? [] : array(payload.expectedPolicyHashes, "expected policy hashes", 64).map(validateExpectedPolicyHash);
|
|
100
|
+
const counters = payload.counters === undefined ? undefined : validateCounters(payload.counters);
|
|
101
|
+
const agents = payload.agents === undefined ? [] : array(payload.agents, "agents", 200).map(validateImportedAgent);
|
|
102
|
+
const tools = payload.tools === undefined ? [] : array(payload.tools, "tools", 200).map(validateImportedTool);
|
|
103
|
+
const identities = payload.identities === undefined ? [] : array(payload.identities, "identities", 200).map(validateImportedIdentity);
|
|
104
|
+
const providers = payload.providers === undefined ? [] : array(payload.providers, "providers", 100).map(validateImportedProvider);
|
|
105
|
+
const resources = payload.resources === undefined ? [] : array(payload.resources, "resources", 300).map(validateImportedResource);
|
|
106
|
+
const sideEffects = payload.sideEffects === undefined ? [] : array(payload.sideEffects, "side effects", 400).map(validateImportedSideEffect);
|
|
107
|
+
const credentials = payload.credentials === undefined ? [] : array(payload.credentials, "credentials", 200).map(validateImportedCredential);
|
|
108
|
+
const policies = payload.policies === undefined ? [] : array(payload.policies, "policies", 50).map(validateImportedPolicy);
|
|
109
|
+
unique(agents.map((agent) => agent.agentId), "agent ids");
|
|
110
|
+
unique(tools.map((tool) => tool.toolId), "tool ids");
|
|
111
|
+
unique(identities.map((identity) => identity.identityId), "identity ids");
|
|
112
|
+
unique(providers.map((provider) => provider.providerId), "provider ids");
|
|
113
|
+
unique(resources.map((resource) => resource.resourceId), "resource ids");
|
|
114
|
+
unique(sideEffects.map((sideEffect) => sideEffect.sideEffectId), "side effect ids");
|
|
115
|
+
unique(credentials.map((credential) => credential.credentialId), "credential ids");
|
|
116
|
+
unique(policies.map((policy) => policy.policyId), "policy ids");
|
|
117
|
+
return {
|
|
118
|
+
schemaVersion: 1,
|
|
119
|
+
kind: "ghostapi.inventory-import",
|
|
120
|
+
source,
|
|
121
|
+
expectedPolicyHashes,
|
|
122
|
+
...(counters === undefined ? {} : { counters }),
|
|
123
|
+
agents,
|
|
124
|
+
tools,
|
|
125
|
+
identities,
|
|
126
|
+
providers,
|
|
127
|
+
resources,
|
|
128
|
+
sideEffects,
|
|
129
|
+
credentials,
|
|
130
|
+
policies
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export function validateState(value) {
|
|
134
|
+
const state = object(value, "Inventory store must be an object.");
|
|
135
|
+
exactKeys(state, ["schemaVersion", "kind", "sources", "importRuns", "agents", "tools", "identities", "providers", "resources", "sideEffects", "credentials", "policies", "edges", "findings", "remediations"], "inventory store");
|
|
136
|
+
if (state.schemaVersion !== INVENTORY_SCHEMA_VERSION || state.kind !== INVENTORY_KIND)
|
|
137
|
+
throw new InventoryError("Unsupported inventory store schema.");
|
|
138
|
+
const sources = array(state.sources, "import sources", INVENTORY_LIMITS.maxSources).map(validateSourceRecord);
|
|
139
|
+
const importRuns = array(state.importRuns, "import runs", INVENTORY_LIMITS.maxImports).map(validateImportRunRecord);
|
|
140
|
+
const agents = array(state.agents, "agents", INVENTORY_LIMITS.maxAgents).map((record) => validateAgentRecord(record));
|
|
141
|
+
const tools = array(state.tools, "tools", INVENTORY_LIMITS.maxTools).map((record) => validateToolRecord(record));
|
|
142
|
+
const identities = array(state.identities, "identities", INVENTORY_LIMITS.maxIdentities).map((record) => validateIdentityRecord(record));
|
|
143
|
+
const providers = array(state.providers, "providers", INVENTORY_LIMITS.maxProviders).map((record) => validateProviderRecord(record));
|
|
144
|
+
const resources = array(state.resources, "resources", INVENTORY_LIMITS.maxResources).map((record) => validateResourceRecord(record));
|
|
145
|
+
const sideEffects = array(state.sideEffects, "side effects", INVENTORY_LIMITS.maxSideEffects).map((record) => validateSideEffectRecord(record));
|
|
146
|
+
const credentials = array(state.credentials, "credentials", INVENTORY_LIMITS.maxCredentials).map((record) => validateCredentialRecord(record));
|
|
147
|
+
const policies = array(state.policies, "policies", INVENTORY_LIMITS.maxPolicies).map((record) => validatePolicyRecord(record));
|
|
148
|
+
const edges = array(state.edges, "graph edges", INVENTORY_LIMITS.maxEdges).map(validateEdge);
|
|
149
|
+
const findings = array(state.findings, "findings", INVENTORY_LIMITS.maxFindings).map(validateFindingRecord);
|
|
150
|
+
const remediations = array(state.remediations, "remediations", INVENTORY_LIMITS.maxRemediations).map(validateRemediationRecord);
|
|
151
|
+
return { schemaVersion: 1, kind: "ghostapi.inventory", sources, importRuns, agents, tools, identities, providers, resources, sideEffects, credentials, policies, edges, findings, remediations };
|
|
152
|
+
}
|
|
153
|
+
export function clone(value) {
|
|
154
|
+
return structuredClone(value);
|
|
155
|
+
}
|
|
156
|
+
export function validateOperator(value) {
|
|
157
|
+
const operator = object(value, "Inventory operator is invalid.");
|
|
158
|
+
exactKeys(operator, ["id", "principalId", "tenantId", "permissions"], "inventory operator");
|
|
159
|
+
const permissions = array(operator.permissions, "inventory operator permissions", 8).map((permission) => {
|
|
160
|
+
if (!isInventoryPermission(permission))
|
|
161
|
+
throw new InventoryError("Inventory operator permission is invalid.");
|
|
162
|
+
return permission;
|
|
163
|
+
});
|
|
164
|
+
unique(permissions, "inventory operator permissions");
|
|
165
|
+
return {
|
|
166
|
+
id: identifier(operator.id, "Inventory operator id"),
|
|
167
|
+
principalId: identifier(operator.principalId, "Inventory operator principal id"),
|
|
168
|
+
tenantId: identifier(operator.tenantId, "Inventory operator tenant id"),
|
|
169
|
+
permissions
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
export function validateFindingsAndRemediations(value) {
|
|
173
|
+
const box = object(value, "Inventory findings and remediations must be an object.");
|
|
174
|
+
exactKeys(box, ["findings", "remediations"], "inventory findings box");
|
|
175
|
+
return {
|
|
176
|
+
findings: array(box.findings, "findings", INVENTORY_LIMITS.maxFindings).map(validateFindingRecord),
|
|
177
|
+
remediations: array(box.remediations, "remediations", INVENTORY_LIMITS.maxRemediations).map(validateRemediationRecord)
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function isInventoryPermission(value) {
|
|
181
|
+
return value === "inventory.import" || value === "inventory.inspect" || value === "inventory.analyze" || value === "inventory.remediate" || value === "inventory.export";
|
|
182
|
+
}
|
|
183
|
+
function validateImportSource(value) {
|
|
184
|
+
const source = object(value, "Inventory import source is invalid.");
|
|
185
|
+
exactKeys(source, ["sourceId", "sourceType", "sourceName", "sourceVersion"], "inventory import source", ["sourceVersion"]);
|
|
186
|
+
const sourceType = source.sourceType;
|
|
187
|
+
if (!IMPORT_SOURCE_TYPES.includes(sourceType))
|
|
188
|
+
throw new InventoryError("Inventory import source type must be config, ci, gateway, or cloud.");
|
|
189
|
+
return {
|
|
190
|
+
sourceId: identifier(source.sourceId, "Import source id"),
|
|
191
|
+
sourceType: sourceType,
|
|
192
|
+
sourceName: text(source.sourceName, "Import source name", 120),
|
|
193
|
+
...(source.sourceVersion === undefined ? {} : { sourceVersion: text(source.sourceVersion, "Import source version", 64) })
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function validateExpectedPolicyHash(value) {
|
|
197
|
+
const expected = object(value, "Expected policy hash is invalid.");
|
|
198
|
+
exactKeys(expected, ["environmentId", "policyHash"], "expected policy hash");
|
|
199
|
+
return { environmentId: identifier(expected.environmentId, "Environment id"), policyHash: hash(expected.policyHash, "Policy hash") };
|
|
200
|
+
}
|
|
201
|
+
function validateCounters(value) {
|
|
202
|
+
const counters = object(value, "Import counters are invalid.");
|
|
203
|
+
exactKeys(counters, ["preventedEgressAttempts", "reviewTimeMinutes", "meanTimeToContainMinutes"], "import counters");
|
|
204
|
+
const result = {};
|
|
205
|
+
for (const key of ["preventedEgressAttempts", "reviewTimeMinutes", "meanTimeToContainMinutes"]) {
|
|
206
|
+
if (counters[key] !== undefined)
|
|
207
|
+
result[key] = nonNegative(counters[key], `Import counter ${key}`, 9_999_999_999);
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
function validateImportedAgent(value) {
|
|
212
|
+
const record = object(value, "Imported agent is invalid.");
|
|
213
|
+
exactKeys(record, ["agentId", "name", "ownerId", "businessPurpose", "model", "runtime", "version", "identityIds", "environmentIds", "gatewayManaged", "killSwitchEnabled", "lastEvidenceAt"], "imported agent", [
|
|
214
|
+
"ownerId", "businessPurpose", "model", "runtime", "version", "lastEvidenceAt"
|
|
215
|
+
]);
|
|
216
|
+
if (typeof record.gatewayManaged !== "boolean" || typeof record.killSwitchEnabled !== "boolean")
|
|
217
|
+
throw new InventoryError("Imported agent booleans are invalid.");
|
|
218
|
+
return {
|
|
219
|
+
agentId: identifier(record.agentId, "Agent id"),
|
|
220
|
+
name: text(record.name, "Agent name", 160),
|
|
221
|
+
...(record.ownerId === undefined ? {} : { ownerId: identifier(record.ownerId, "Agent owner id") }),
|
|
222
|
+
...(record.businessPurpose === undefined ? {} : { businessPurpose: text(record.businessPurpose, "Agent business purpose", 240) }),
|
|
223
|
+
...(record.model === undefined ? {} : { model: text(record.model, "Agent model", 120) }),
|
|
224
|
+
...(record.runtime === undefined ? {} : { runtime: text(record.runtime, "Agent runtime", 120) }),
|
|
225
|
+
...(record.version === undefined ? {} : { version: text(record.version, "Agent version", 64) }),
|
|
226
|
+
identityIds: identifiers(record.identityIds, "Agent identity ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
227
|
+
environmentIds: identifiers(record.environmentIds, "Agent environment ids", 16),
|
|
228
|
+
gatewayManaged: record.gatewayManaged,
|
|
229
|
+
killSwitchEnabled: record.killSwitchEnabled,
|
|
230
|
+
...(record.lastEvidenceAt === undefined ? {} : { lastEvidenceAt: timestamp(record.lastEvidenceAt, "Agent last evidence time") })
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function validateImportedTool(value) {
|
|
234
|
+
const record = object(value, "Imported tool is invalid.");
|
|
235
|
+
exactKeys(record, ["toolId", "name", "kind", "providerId", "requiredScopes", "credentialIds"], "imported tool", ["providerId"]);
|
|
236
|
+
const kind = record.kind;
|
|
237
|
+
if (!TOOL_KINDS.includes(kind))
|
|
238
|
+
throw new InventoryError("Imported tool kind is invalid.");
|
|
239
|
+
return {
|
|
240
|
+
toolId: identifier(record.toolId, "Tool id"),
|
|
241
|
+
name: text(record.name, "Tool name", 160),
|
|
242
|
+
kind: kind,
|
|
243
|
+
...(record.providerId === undefined ? {} : { providerId: identifier(record.providerId, "Tool provider id") }),
|
|
244
|
+
requiredScopes: scopes(record.requiredScopes, "Tool required scopes", INVENTORY_LIMITS.maxScopesPerRecord),
|
|
245
|
+
credentialIds: identifiers(record.credentialIds, "Tool credential ids", INVENTORY_LIMITS.maxReferencesPerRecord)
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function validateImportedIdentity(value) {
|
|
249
|
+
const record = object(value, "Imported identity is invalid.");
|
|
250
|
+
exactKeys(record, ["identityId", "principalId", "role", "agentId", "toolIds", "environmentIds", "scopes"], "imported identity", ["agentId"]);
|
|
251
|
+
const role = record.role;
|
|
252
|
+
if (!IDENTITY_ROLES.includes(role))
|
|
253
|
+
throw new InventoryError("Imported identity role is invalid.");
|
|
254
|
+
return {
|
|
255
|
+
identityId: identifier(record.identityId, "Identity id"),
|
|
256
|
+
principalId: identifier(record.principalId, "Identity principal id"),
|
|
257
|
+
role: role,
|
|
258
|
+
...(record.agentId === undefined ? {} : { agentId: identifier(record.agentId, "Identity agent id") }),
|
|
259
|
+
toolIds: identifiers(record.toolIds, "Identity tool ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
260
|
+
environmentIds: identifiers(record.environmentIds, "Identity environment ids", 16),
|
|
261
|
+
scopes: scopes(record.scopes, "Identity scopes", INVENTORY_LIMITS.maxScopesPerRecord)
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function validateImportedProvider(value) {
|
|
265
|
+
const record = object(value, "Imported provider is invalid.");
|
|
266
|
+
exactKeys(record, ["providerId", "name", "providerType", "ownerId", "environmentIds", "resourceIds", "gatewayBound", "killSwitchApplied"], "imported provider", ["ownerId"]);
|
|
267
|
+
if (typeof record.gatewayBound !== "boolean" || typeof record.killSwitchApplied !== "boolean")
|
|
268
|
+
throw new InventoryError("Imported provider booleans are invalid.");
|
|
269
|
+
return {
|
|
270
|
+
providerId: identifier(record.providerId, "Provider id"),
|
|
271
|
+
name: text(record.name, "Provider name", 160),
|
|
272
|
+
providerType: text(record.providerType, "Provider type", 120),
|
|
273
|
+
...(record.ownerId === undefined ? {} : { ownerId: identifier(record.ownerId, "Provider owner id") }),
|
|
274
|
+
environmentIds: identifiers(record.environmentIds, "Provider environment ids", 16),
|
|
275
|
+
resourceIds: identifiers(record.resourceIds, "Provider resource ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
276
|
+
gatewayBound: record.gatewayBound,
|
|
277
|
+
killSwitchApplied: record.killSwitchApplied
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function validateImportedResource(value) {
|
|
281
|
+
const record = object(value, "Imported resource is invalid.");
|
|
282
|
+
exactKeys(record, ["resourceId", "providerId", "resourceType", "name", "environmentIds", "sensitive", "ownerId", "businessPurpose", "sideEffectIds"], "imported resource", ["ownerId", "businessPurpose"]);
|
|
283
|
+
if (typeof record.sensitive !== "boolean")
|
|
284
|
+
throw new InventoryError("Imported resource sensitive flag is invalid.");
|
|
285
|
+
return {
|
|
286
|
+
resourceId: identifier(record.resourceId, "Resource id"),
|
|
287
|
+
providerId: identifier(record.providerId, "Resource provider id"),
|
|
288
|
+
resourceType: text(record.resourceType, "Resource type", 120),
|
|
289
|
+
name: text(record.name, "Resource name", 160),
|
|
290
|
+
environmentIds: identifiers(record.environmentIds, "Resource environment ids", 16),
|
|
291
|
+
sensitive: record.sensitive,
|
|
292
|
+
...(record.ownerId === undefined ? {} : { ownerId: identifier(record.ownerId, "Resource owner id") }),
|
|
293
|
+
...(record.businessPurpose === undefined ? {} : { businessPurpose: text(record.businessPurpose, "Resource business purpose", 240) }),
|
|
294
|
+
sideEffectIds: identifiers(record.sideEffectIds, "Resource side effect ids", INVENTORY_LIMITS.maxReferencesPerRecord)
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function validateImportedSideEffect(value) {
|
|
298
|
+
const record = object(value, "Imported side effect is invalid.");
|
|
299
|
+
exactKeys(record, ["sideEffectId", "resourceId", "kind", "severity"], "imported side effect");
|
|
300
|
+
const kind = record.kind;
|
|
301
|
+
const severity = record.severity;
|
|
302
|
+
if (!SIDE_EFFECT_KINDS.includes(kind))
|
|
303
|
+
throw new InventoryError("Imported side effect kind is invalid.");
|
|
304
|
+
if (!IMPACT_SEVERITIES.includes(severity))
|
|
305
|
+
throw new InventoryError("Imported side effect severity is invalid.");
|
|
306
|
+
return {
|
|
307
|
+
sideEffectId: identifier(record.sideEffectId, "Side effect id"),
|
|
308
|
+
resourceId: identifier(record.resourceId, "Side effect resource id"),
|
|
309
|
+
kind: kind,
|
|
310
|
+
severity: severity
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function validateImportedCredential(value) {
|
|
314
|
+
const record = object(value, "Imported credential is invalid.");
|
|
315
|
+
exactKeys(record, ["credentialId", "name", "providerId", "ownerId", "environmentIds", "grantScopes", "toolIds", "status", "issuedAt", "lastUsedAt", "rotatedAt", "gatewayBound"], "imported credential", [
|
|
316
|
+
"ownerId", "lastUsedAt", "rotatedAt"
|
|
317
|
+
]);
|
|
318
|
+
if (record.status !== "active" && record.status !== "revoked")
|
|
319
|
+
throw new InventoryError("Imported credential status is invalid.");
|
|
320
|
+
if (typeof record.gatewayBound !== "boolean")
|
|
321
|
+
throw new InventoryError("Imported credential gatewayBound flag is invalid.");
|
|
322
|
+
return {
|
|
323
|
+
credentialId: identifier(record.credentialId, "Credential id"),
|
|
324
|
+
name: text(record.name, "Credential name", 160),
|
|
325
|
+
providerId: identifier(record.providerId, "Credential provider id"),
|
|
326
|
+
...(record.ownerId === undefined ? {} : { ownerId: identifier(record.ownerId, "Credential owner id") }),
|
|
327
|
+
environmentIds: identifiers(record.environmentIds, "Credential environment ids", 16),
|
|
328
|
+
grantScopes: scopes(record.grantScopes, "Credential grant scopes", INVENTORY_LIMITS.maxScopesPerRecord),
|
|
329
|
+
toolIds: identifiers(record.toolIds, "Credential tool ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
330
|
+
status: record.status,
|
|
331
|
+
issuedAt: timestamp(record.issuedAt, "Credential issued time"),
|
|
332
|
+
...(record.lastUsedAt === undefined ? {} : { lastUsedAt: timestamp(record.lastUsedAt, "Credential last used time") }),
|
|
333
|
+
...(record.rotatedAt === undefined ? {} : { rotatedAt: timestamp(record.rotatedAt, "Credential rotated time") }),
|
|
334
|
+
gatewayBound: record.gatewayBound
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
function validateImportedPolicy(value) {
|
|
338
|
+
const record = object(value, "Imported policy is invalid.");
|
|
339
|
+
exactKeys(record, ["policyId", "name", "version", "hash", "environmentIds"], "imported policy");
|
|
340
|
+
return {
|
|
341
|
+
policyId: identifier(record.policyId, "Policy id"),
|
|
342
|
+
name: text(record.name, "Policy name", 160),
|
|
343
|
+
version: text(record.version, "Policy version", 64),
|
|
344
|
+
hash: hash(record.hash, "Policy hash"),
|
|
345
|
+
environmentIds: identifiers(record.environmentIds, "Policy environment ids", 16)
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function validateSourceRecord(value) {
|
|
349
|
+
const record = object(value, "Import source is invalid.");
|
|
350
|
+
exactKeys(record, ["tenantId", "sourceId", "sourceType", "sourceName", "sourceVersion", "firstSeenAt", "lastSeenAt", "lastDigest"], "import source", ["sourceVersion"]);
|
|
351
|
+
const sourceType = record.sourceType;
|
|
352
|
+
if (!IMPORT_SOURCE_TYPES.includes(sourceType))
|
|
353
|
+
throw new InventoryError("Import source type is invalid.");
|
|
354
|
+
return {
|
|
355
|
+
tenantId: identifier(record.tenantId, "Source tenant id"),
|
|
356
|
+
sourceId: identifier(record.sourceId, "Source id"),
|
|
357
|
+
sourceType: sourceType,
|
|
358
|
+
sourceName: text(record.sourceName, "Source name", 120),
|
|
359
|
+
...(record.sourceVersion === undefined ? {} : { sourceVersion: text(record.sourceVersion, "Source version", 64) }),
|
|
360
|
+
firstSeenAt: timestamp(record.firstSeenAt, "Source first seen time"),
|
|
361
|
+
lastSeenAt: timestamp(record.lastSeenAt, "Source last seen time"),
|
|
362
|
+
lastDigest: hash(record.lastDigest, "Source digest")
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function validateImportRunRecord(value) {
|
|
366
|
+
const record = object(value, "Import run is invalid.");
|
|
367
|
+
exactKeys(record, ["schemaVersion", "kind", "tenantId", "runId", "importedAt", "importedBy", "source", "digest", "expectedPolicyHashes", "counters", "recordCounts", "edgesCreated", "edgesRefreshed", "status"], "import run", ["counters"]);
|
|
368
|
+
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION || record.kind !== "ghostapi.inventory-import-run")
|
|
369
|
+
throw new InventoryError("Unsupported import run schema.");
|
|
370
|
+
if (record.status !== "completed")
|
|
371
|
+
throw new InventoryError("Import run status is invalid.");
|
|
372
|
+
const source = object(record.source, "Import run source is invalid.");
|
|
373
|
+
exactKeys(source, ["sourceId", "sourceType", "sourceName", "sourceVersion"], "import run source", ["sourceVersion"]);
|
|
374
|
+
const sourceType = source.sourceType;
|
|
375
|
+
if (!IMPORT_SOURCE_TYPES.includes(sourceType))
|
|
376
|
+
throw new InventoryError("Import run source type is invalid.");
|
|
377
|
+
const recordCounts = object(record.recordCounts, "Import run record counts are invalid.");
|
|
378
|
+
exactKeys(recordCounts, ["agents", "tools", "identities", "providers", "resources", "sideEffects", "credentials", "policies"], "import run record counts");
|
|
379
|
+
const counters = record.counters === undefined ? undefined : validateCounters(record.counters);
|
|
380
|
+
if (sourceType === "config" && counters !== undefined)
|
|
381
|
+
throw new InventoryError("Counters are only allowed for ci, gateway, or cloud imports.");
|
|
382
|
+
return {
|
|
383
|
+
schemaVersion: 1,
|
|
384
|
+
kind: "ghostapi.inventory-import-run",
|
|
385
|
+
tenantId: identifier(record.tenantId, "Import run tenant id"),
|
|
386
|
+
runId: identifier(record.runId, "Import run id"),
|
|
387
|
+
importedAt: timestamp(record.importedAt, "Import run time"),
|
|
388
|
+
importedBy: identifier(record.importedBy, "Import run importer"),
|
|
389
|
+
source: {
|
|
390
|
+
sourceId: identifier(source.sourceId, "Import run source id"),
|
|
391
|
+
sourceType: sourceType,
|
|
392
|
+
sourceName: text(source.sourceName, "Import run source name", 120),
|
|
393
|
+
...(source.sourceVersion === undefined ? {} : { sourceVersion: text(source.sourceVersion, "Import run source version", 64) })
|
|
394
|
+
},
|
|
395
|
+
digest: hash(record.digest, "Import run digest"),
|
|
396
|
+
expectedPolicyHashes: array(record.expectedPolicyHashes, "import run expected policy hashes", 64).map(validateExpectedPolicyHash),
|
|
397
|
+
...(counters === undefined ? {} : { counters }),
|
|
398
|
+
recordCounts: {
|
|
399
|
+
agents: nonNegative(recordCounts.agents, "Import run agent count", 500),
|
|
400
|
+
tools: nonNegative(recordCounts.tools, "Import run tool count", 500),
|
|
401
|
+
identities: nonNegative(recordCounts.identities, "Import run identity count", 500),
|
|
402
|
+
providers: nonNegative(recordCounts.providers, "Import run provider count", 200),
|
|
403
|
+
resources: nonNegative(recordCounts.resources, "Import run resource count", 1000),
|
|
404
|
+
sideEffects: nonNegative(recordCounts.sideEffects, "Import run side effect count", 2000),
|
|
405
|
+
credentials: nonNegative(recordCounts.credentials, "Import run credential count", 500),
|
|
406
|
+
policies: nonNegative(recordCounts.policies, "Import run policy count", 100)
|
|
407
|
+
},
|
|
408
|
+
edgesCreated: nonNegative(record.edgesCreated, "Import run edges created", 6000),
|
|
409
|
+
edgesRefreshed: nonNegative(record.edgesRefreshed, "Import run edges refreshed", 6000),
|
|
410
|
+
status: "completed"
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function validateAgentRecord(value) {
|
|
414
|
+
const record = object(value, "Agent record is invalid.");
|
|
415
|
+
exactKeys(record, ["schemaVersion", "kind", "tenantId", "agentId", "name", "ownerId", "businessPurpose", "model", "runtime", "version", "identityIds", "environmentIds", "gatewayManaged", "killSwitchEnabled", "lastEvidenceAt", "provenance", "freshness"], "agent record", [
|
|
416
|
+
"businessPurpose", "model", "runtime", "version"
|
|
417
|
+
]);
|
|
418
|
+
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION || record.kind !== "ghostapi.inventory-agent")
|
|
419
|
+
throw new InventoryError("Unsupported agent record schema.");
|
|
420
|
+
if (typeof record.gatewayManaged !== "boolean" || typeof record.killSwitchEnabled !== "boolean")
|
|
421
|
+
throw new InventoryError("Agent record booleans are invalid.");
|
|
422
|
+
return {
|
|
423
|
+
schemaVersion: 1,
|
|
424
|
+
kind: "ghostapi.inventory-agent",
|
|
425
|
+
tenantId: identifier(record.tenantId, "Agent tenant id"),
|
|
426
|
+
agentId: identifier(record.agentId, "Agent id"),
|
|
427
|
+
name: text(record.name, "Agent name", 160),
|
|
428
|
+
ownerId: record.ownerId === null ? null : record.ownerId === undefined ? null : identifier(record.ownerId, "Agent owner id"),
|
|
429
|
+
...(record.businessPurpose === undefined ? {} : { businessPurpose: text(record.businessPurpose, "Agent business purpose", 240) }),
|
|
430
|
+
...(record.model === undefined ? {} : { model: text(record.model, "Agent model", 120) }),
|
|
431
|
+
...(record.runtime === undefined ? {} : { runtime: text(record.runtime, "Agent runtime", 120) }),
|
|
432
|
+
...(record.version === undefined ? {} : { version: text(record.version, "Agent version", 64) }),
|
|
433
|
+
identityIds: identifiers(record.identityIds, "Agent identity ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
434
|
+
environmentIds: identifiers(record.environmentIds, "Agent environment ids", 16),
|
|
435
|
+
gatewayManaged: record.gatewayManaged,
|
|
436
|
+
killSwitchEnabled: record.killSwitchEnabled,
|
|
437
|
+
lastEvidenceAt: record.lastEvidenceAt === null ? null : record.lastEvidenceAt === undefined ? null : timestamp(record.lastEvidenceAt, "Agent last evidence time"),
|
|
438
|
+
provenance: validateProvenance(record.provenance),
|
|
439
|
+
freshness: validateFreshness(record.freshness)
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function validateToolRecord(value) {
|
|
443
|
+
const record = object(value, "Tool record is invalid.");
|
|
444
|
+
exactKeys(record, ["schemaVersion", "kind", "tenantId", "toolId", "name", "providerId", "requiredScopes", "credentialIds", "provenance", "freshness"], "tool record", ["providerId"]);
|
|
445
|
+
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION)
|
|
446
|
+
throw new InventoryError("Unsupported tool record schema.");
|
|
447
|
+
const toolKind = record.kind;
|
|
448
|
+
if (!TOOL_KINDS.includes(toolKind))
|
|
449
|
+
throw new InventoryError("Tool record kind is invalid.");
|
|
450
|
+
return {
|
|
451
|
+
schemaVersion: 1,
|
|
452
|
+
tenantId: identifier(record.tenantId, "Tool tenant id"),
|
|
453
|
+
toolId: identifier(record.toolId, "Tool id"),
|
|
454
|
+
name: text(record.name, "Tool name", 160),
|
|
455
|
+
kind: toolKind,
|
|
456
|
+
providerId: record.providerId === null ? null : record.providerId === undefined ? null : identifier(record.providerId, "Tool provider id"),
|
|
457
|
+
requiredScopes: scopes(record.requiredScopes, "Tool required scopes", INVENTORY_LIMITS.maxScopesPerRecord),
|
|
458
|
+
credentialIds: identifiers(record.credentialIds, "Tool credential ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
459
|
+
provenance: validateProvenance(record.provenance),
|
|
460
|
+
freshness: validateFreshness(record.freshness)
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function validateIdentityRecord(value) {
|
|
464
|
+
const record = object(value, "Identity record is invalid.");
|
|
465
|
+
exactKeys(record, ["schemaVersion", "kind", "tenantId", "identityId", "principalId", "role", "agentId", "toolIds", "environmentIds", "scopes", "provenance", "freshness"], "identity record", ["agentId"]);
|
|
466
|
+
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION || record.kind !== "ghostapi.inventory-identity")
|
|
467
|
+
throw new InventoryError("Unsupported identity record schema.");
|
|
468
|
+
const role = record.role;
|
|
469
|
+
if (!IDENTITY_ROLES.includes(role))
|
|
470
|
+
throw new InventoryError("Identity record role is invalid.");
|
|
471
|
+
return {
|
|
472
|
+
schemaVersion: 1,
|
|
473
|
+
kind: "ghostapi.inventory-identity",
|
|
474
|
+
tenantId: identifier(record.tenantId, "Identity tenant id"),
|
|
475
|
+
identityId: identifier(record.identityId, "Identity id"),
|
|
476
|
+
principalId: identifier(record.principalId, "Identity principal id"),
|
|
477
|
+
role: role,
|
|
478
|
+
agentId: record.agentId === null ? null : record.agentId === undefined ? null : identifier(record.agentId, "Identity agent id"),
|
|
479
|
+
toolIds: identifiers(record.toolIds, "Identity tool ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
480
|
+
environmentIds: identifiers(record.environmentIds, "Identity environment ids", 16),
|
|
481
|
+
scopes: scopes(record.scopes, "Identity scopes", INVENTORY_LIMITS.maxScopesPerRecord),
|
|
482
|
+
provenance: validateProvenance(record.provenance),
|
|
483
|
+
freshness: validateFreshness(record.freshness)
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
function validateProviderRecord(value) {
|
|
487
|
+
const record = object(value, "Provider record is invalid.");
|
|
488
|
+
exactKeys(record, ["schemaVersion", "kind", "tenantId", "providerId", "name", "providerType", "ownerId", "environmentIds", "resourceIds", "gatewayBound", "killSwitchApplied", "provenance", "freshness"], "provider record", ["ownerId"]);
|
|
489
|
+
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION || record.kind !== "ghostapi.inventory-provider")
|
|
490
|
+
throw new InventoryError("Unsupported provider record schema.");
|
|
491
|
+
if (typeof record.gatewayBound !== "boolean" || typeof record.killSwitchApplied !== "boolean")
|
|
492
|
+
throw new InventoryError("Provider record booleans are invalid.");
|
|
493
|
+
return {
|
|
494
|
+
schemaVersion: 1,
|
|
495
|
+
kind: "ghostapi.inventory-provider",
|
|
496
|
+
tenantId: identifier(record.tenantId, "Provider tenant id"),
|
|
497
|
+
providerId: identifier(record.providerId, "Provider id"),
|
|
498
|
+
name: text(record.name, "Provider name", 160),
|
|
499
|
+
providerType: text(record.providerType, "Provider type", 120),
|
|
500
|
+
ownerId: record.ownerId === null ? null : record.ownerId === undefined ? null : identifier(record.ownerId, "Provider owner id"),
|
|
501
|
+
environmentIds: identifiers(record.environmentIds, "Provider environment ids", 16),
|
|
502
|
+
resourceIds: identifiers(record.resourceIds, "Provider resource ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
503
|
+
gatewayBound: record.gatewayBound,
|
|
504
|
+
killSwitchApplied: record.killSwitchApplied,
|
|
505
|
+
provenance: validateProvenance(record.provenance),
|
|
506
|
+
freshness: validateFreshness(record.freshness)
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
function validateResourceRecord(value) {
|
|
510
|
+
const record = object(value, "Resource record is invalid.");
|
|
511
|
+
exactKeys(record, ["schemaVersion", "kind", "tenantId", "resourceId", "providerId", "resourceType", "name", "environmentIds", "sensitive", "ownerId", "businessPurpose", "sideEffectIds", "provenance", "freshness"], "resource record", ["ownerId", "businessPurpose"]);
|
|
512
|
+
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION || record.kind !== "ghostapi.inventory-resource")
|
|
513
|
+
throw new InventoryError("Unsupported resource record schema.");
|
|
514
|
+
if (typeof record.sensitive !== "boolean")
|
|
515
|
+
throw new InventoryError("Resource record sensitive flag is invalid.");
|
|
516
|
+
return {
|
|
517
|
+
schemaVersion: 1,
|
|
518
|
+
kind: "ghostapi.inventory-resource",
|
|
519
|
+
tenantId: identifier(record.tenantId, "Resource tenant id"),
|
|
520
|
+
resourceId: identifier(record.resourceId, "Resource id"),
|
|
521
|
+
providerId: identifier(record.providerId, "Resource provider id"),
|
|
522
|
+
resourceType: text(record.resourceType, "Resource type", 120),
|
|
523
|
+
name: text(record.name, "Resource name", 160),
|
|
524
|
+
environmentIds: identifiers(record.environmentIds, "Resource environment ids", 16),
|
|
525
|
+
sensitive: record.sensitive,
|
|
526
|
+
ownerId: record.ownerId === null ? null : record.ownerId === undefined ? null : identifier(record.ownerId, "Resource owner id"),
|
|
527
|
+
...(record.businessPurpose === undefined ? {} : { businessPurpose: text(record.businessPurpose, "Resource business purpose", 240) }),
|
|
528
|
+
sideEffectIds: identifiers(record.sideEffectIds, "Resource side effect ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
529
|
+
provenance: validateProvenance(record.provenance),
|
|
530
|
+
freshness: validateFreshness(record.freshness)
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
function validateSideEffectRecord(value) {
|
|
534
|
+
const record = object(value, "Side effect record is invalid.");
|
|
535
|
+
exactKeys(record, ["schemaVersion", "tenantId", "sideEffectId", "resourceId", "kind", "severity", "provenance", "freshness"], "side effect record");
|
|
536
|
+
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION)
|
|
537
|
+
throw new InventoryError("Unsupported side effect record schema.");
|
|
538
|
+
const kind = record.kind;
|
|
539
|
+
const severity = record.severity;
|
|
540
|
+
if (!SIDE_EFFECT_KINDS.includes(kind))
|
|
541
|
+
throw new InventoryError("Side effect record kind is invalid.");
|
|
542
|
+
if (!IMPACT_SEVERITIES.includes(severity))
|
|
543
|
+
throw new InventoryError("Side effect record severity is invalid.");
|
|
544
|
+
return {
|
|
545
|
+
schemaVersion: 1,
|
|
546
|
+
tenantId: identifier(record.tenantId, "Side effect tenant id"),
|
|
547
|
+
sideEffectId: identifier(record.sideEffectId, "Side effect id"),
|
|
548
|
+
resourceId: identifier(record.resourceId, "Side effect resource id"),
|
|
549
|
+
kind: kind,
|
|
550
|
+
severity: severity,
|
|
551
|
+
provenance: validateProvenance(record.provenance),
|
|
552
|
+
freshness: validateFreshness(record.freshness)
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
function validateCredentialRecord(value) {
|
|
556
|
+
const record = object(value, "Credential record is invalid.");
|
|
557
|
+
exactKeys(record, ["schemaVersion", "kind", "tenantId", "credentialId", "name", "providerId", "ownerId", "environmentIds", "grantScopes", "toolIds", "status", "issuedAt", "lastUsedAt", "rotatedAt", "gatewayBound", "provenance", "freshness"], "credential record", ["ownerId", "lastUsedAt", "rotatedAt"]);
|
|
558
|
+
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION || record.kind !== "ghostapi.inventory-credential")
|
|
559
|
+
throw new InventoryError("Unsupported credential record schema.");
|
|
560
|
+
if (record.status !== "active" && record.status !== "revoked")
|
|
561
|
+
throw new InventoryError("Credential record status is invalid.");
|
|
562
|
+
if (typeof record.gatewayBound !== "boolean")
|
|
563
|
+
throw new InventoryError("Credential record gatewayBound flag is invalid.");
|
|
564
|
+
return {
|
|
565
|
+
schemaVersion: 1,
|
|
566
|
+
kind: "ghostapi.inventory-credential",
|
|
567
|
+
tenantId: identifier(record.tenantId, "Credential tenant id"),
|
|
568
|
+
credentialId: identifier(record.credentialId, "Credential id"),
|
|
569
|
+
name: text(record.name, "Credential name", 160),
|
|
570
|
+
providerId: identifier(record.providerId, "Credential provider id"),
|
|
571
|
+
ownerId: record.ownerId === null ? null : record.ownerId === undefined ? null : identifier(record.ownerId, "Credential owner id"),
|
|
572
|
+
environmentIds: identifiers(record.environmentIds, "Credential environment ids", 16),
|
|
573
|
+
grantScopes: scopes(record.grantScopes, "Credential grant scopes", INVENTORY_LIMITS.maxScopesPerRecord),
|
|
574
|
+
toolIds: identifiers(record.toolIds, "Credential tool ids", INVENTORY_LIMITS.maxReferencesPerRecord),
|
|
575
|
+
status: record.status,
|
|
576
|
+
issuedAt: timestamp(record.issuedAt, "Credential issued time"),
|
|
577
|
+
lastUsedAt: record.lastUsedAt === null ? null : record.lastUsedAt === undefined ? null : timestamp(record.lastUsedAt, "Credential last used time"),
|
|
578
|
+
...(record.rotatedAt === undefined ? {} : { rotatedAt: timestamp(record.rotatedAt, "Credential rotated time") }),
|
|
579
|
+
gatewayBound: record.gatewayBound,
|
|
580
|
+
provenance: validateProvenance(record.provenance),
|
|
581
|
+
freshness: validateFreshness(record.freshness)
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function validatePolicyRecord(value) {
|
|
585
|
+
const record = object(value, "Policy record is invalid.");
|
|
586
|
+
exactKeys(record, ["schemaVersion", "kind", "tenantId", "policyId", "name", "version", "hash", "environmentIds", "provenance", "freshness"], "policy record");
|
|
587
|
+
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION || record.kind !== "ghostapi.inventory-policy")
|
|
588
|
+
throw new InventoryError("Unsupported policy record schema.");
|
|
589
|
+
return {
|
|
590
|
+
schemaVersion: 1,
|
|
591
|
+
kind: "ghostapi.inventory-policy",
|
|
592
|
+
tenantId: identifier(record.tenantId, "Policy tenant id"),
|
|
593
|
+
policyId: identifier(record.policyId, "Policy id"),
|
|
594
|
+
name: text(record.name, "Policy name", 160),
|
|
595
|
+
version: text(record.version, "Policy version", 64),
|
|
596
|
+
hash: hash(record.hash, "Policy hash"),
|
|
597
|
+
environmentIds: identifiers(record.environmentIds, "Policy environment ids", 16),
|
|
598
|
+
provenance: validateProvenance(record.provenance),
|
|
599
|
+
freshness: validateFreshness(record.freshness)
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
function validateEdge(value) {
|
|
603
|
+
const record = object(value, "Graph edge is invalid.");
|
|
604
|
+
exactKeys(record, ["tenantId", "edgeId", "sourceKind", "sourceId", "targetKind", "targetId", "relation", "provenance", "firstSeenAt", "lastSeenAt"], "graph edge");
|
|
605
|
+
if (!NODE_KINDS.includes(record.sourceKind))
|
|
606
|
+
throw new InventoryError("Graph edge source kind is invalid.");
|
|
607
|
+
if (!NODE_KINDS.includes(record.targetKind))
|
|
608
|
+
throw new InventoryError("Graph edge target kind is invalid.");
|
|
609
|
+
if (!EDGE_RELATIONS.includes(record.relation))
|
|
610
|
+
throw new InventoryError("Graph edge relation is invalid.");
|
|
611
|
+
return {
|
|
612
|
+
tenantId: identifier(record.tenantId, "Edge tenant id"),
|
|
613
|
+
edgeId: identifier(record.edgeId, "Edge id"),
|
|
614
|
+
sourceKind: record.sourceKind,
|
|
615
|
+
sourceId: identifier(record.sourceId, "Edge source id"),
|
|
616
|
+
targetKind: record.targetKind,
|
|
617
|
+
targetId: identifier(record.targetId, "Edge target id"),
|
|
618
|
+
relation: record.relation,
|
|
619
|
+
provenance: validateProvenance(record.provenance),
|
|
620
|
+
firstSeenAt: timestamp(record.firstSeenAt, "Edge first seen time"),
|
|
621
|
+
lastSeenAt: timestamp(record.lastSeenAt, "Edge last seen time")
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
function validateFindingRecord(value) {
|
|
625
|
+
const record = object(value, "Finding record is invalid.");
|
|
626
|
+
exactKeys(record, ["schemaVersion", "tenantId", "findingId", "kind", "severity", "targetKind", "targetId", "ruleId", "reason", "basis", "discoveredAt", "status", "resolvedAt", "remediationId"], "finding record", ["resolvedAt", "remediationId"]);
|
|
627
|
+
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION)
|
|
628
|
+
throw new InventoryError("Unsupported finding record schema.");
|
|
629
|
+
if (!FINDING_KINDS.includes(record.kind))
|
|
630
|
+
throw new InventoryError("Finding kind is invalid.");
|
|
631
|
+
if (!FINDING_SEVERITIES.includes(record.severity))
|
|
632
|
+
throw new InventoryError("Finding severity is invalid.");
|
|
633
|
+
if (!FINDING_BASIS.includes(record.basis))
|
|
634
|
+
throw new InventoryError("Finding basis is invalid.");
|
|
635
|
+
if (record.status !== "open" && record.status !== "resolved")
|
|
636
|
+
throw new InventoryError("Finding status is invalid.");
|
|
637
|
+
if (record.targetKind !== "agent" && record.targetKind !== "identity" && record.targetKind !== "tool" && record.targetKind !== "provider" && record.targetKind !== "resource" && record.targetKind !== "side_effect" && record.targetKind !== "credential" && record.targetKind !== "environment" && record.targetKind !== "policy")
|
|
638
|
+
throw new InventoryError("Finding target kind is invalid.");
|
|
639
|
+
return {
|
|
640
|
+
schemaVersion: 1,
|
|
641
|
+
tenantId: identifier(record.tenantId, "Finding tenant id"),
|
|
642
|
+
findingId: identifier(record.findingId, "Finding id"),
|
|
643
|
+
kind: record.kind,
|
|
644
|
+
severity: record.severity,
|
|
645
|
+
targetKind: record.targetKind,
|
|
646
|
+
targetId: identifier(record.targetId, "Finding target id"),
|
|
647
|
+
ruleId: identifier(record.ruleId, "Finding rule id"),
|
|
648
|
+
reason: text(record.reason, "Finding reason", 400),
|
|
649
|
+
basis: record.basis,
|
|
650
|
+
discoveredAt: timestamp(record.discoveredAt, "Finding discovery time"),
|
|
651
|
+
status: record.status,
|
|
652
|
+
...(record.resolvedAt === undefined ? {} : { resolvedAt: timestamp(record.resolvedAt, "Finding resolved time") }),
|
|
653
|
+
...(record.remediationId === undefined ? {} : { remediationId: identifier(record.remediationId, "Finding remediation id") })
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
function validateRemediationRecord(value) {
|
|
657
|
+
const record = object(value, "Remediation record is invalid.");
|
|
658
|
+
exactKeys(record, ["schemaVersion", "tenantId", "remediationId", "findingId", "kind", "targetKind", "targetId", "rationale", "proposedBy", "createdAt", "status", "appliedAt", "result"], "remediation record", ["appliedAt", "result"]);
|
|
659
|
+
if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION)
|
|
660
|
+
throw new InventoryError("Unsupported remediation record schema.");
|
|
661
|
+
if (!REMEDIATION_KINDS.includes(record.kind))
|
|
662
|
+
throw new InventoryError("Remediation kind is invalid.");
|
|
663
|
+
if (!REMEDIATION_STATUSES.includes(record.status))
|
|
664
|
+
throw new InventoryError("Remediation status is invalid.");
|
|
665
|
+
if (record.targetKind !== "agent" && record.targetKind !== "identity" && record.targetKind !== "tool" && record.targetKind !== "provider" && record.targetKind !== "resource" && record.targetKind !== "side_effect" && record.targetKind !== "credential" && record.targetKind !== "environment" && record.targetKind !== "policy")
|
|
666
|
+
throw new InventoryError("Remediation target kind is invalid.");
|
|
667
|
+
const result = record.result === undefined ? undefined : validateRemediationResult(record.result);
|
|
668
|
+
return {
|
|
669
|
+
schemaVersion: 1,
|
|
670
|
+
tenantId: identifier(record.tenantId, "Remediation tenant id"),
|
|
671
|
+
remediationId: identifier(record.remediationId, "Remediation id"),
|
|
672
|
+
findingId: identifier(record.findingId, "Remediation finding id"),
|
|
673
|
+
kind: record.kind,
|
|
674
|
+
targetKind: record.targetKind,
|
|
675
|
+
targetId: identifier(record.targetId, "Remediation target id"),
|
|
676
|
+
rationale: text(record.rationale, "Remediation rationale", 400),
|
|
677
|
+
proposedBy: identifier(record.proposedBy, "Remediation proposer"),
|
|
678
|
+
createdAt: timestamp(record.createdAt, "Remediation creation time"),
|
|
679
|
+
status: record.status,
|
|
680
|
+
...(record.appliedAt === undefined ? {} : { appliedAt: timestamp(record.appliedAt, "Remediation applied time") }),
|
|
681
|
+
...(result === undefined ? {} : { result })
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
function validateRemediationResult(value) {
|
|
685
|
+
const result = object(value, "Remediation result is invalid.");
|
|
686
|
+
exactKeys(result, ["description", "reducedScopes", "evalScenarioId", "ownerId"], "remediation result", ["reducedScopes", "evalScenarioId", "ownerId"]);
|
|
687
|
+
return {
|
|
688
|
+
description: text(result.description, "Remediation result description", 400),
|
|
689
|
+
...(result.reducedScopes === undefined ? {} : { reducedScopes: scopes(result.reducedScopes, "Remediation reduced scopes", INVENTORY_LIMITS.maxScopesPerRecord) }),
|
|
690
|
+
...(result.evalScenarioId === undefined ? {} : { evalScenarioId: identifier(result.evalScenarioId, "Remediation eval scenario id") }),
|
|
691
|
+
...(result.ownerId === undefined ? {} : { ownerId: identifier(result.ownerId, "Remediation owner id") })
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function validateProvenance(value) {
|
|
695
|
+
const provenance = object(value, "Record provenance is invalid.");
|
|
696
|
+
exactKeys(provenance, ["sourceId", "sourceType", "sourceName", "importedAt", "importedBy"], "record provenance");
|
|
697
|
+
const sourceType = provenance.sourceType;
|
|
698
|
+
if (!IMPORT_SOURCE_TYPES.includes(sourceType))
|
|
699
|
+
throw new InventoryError("Record provenance source type is invalid.");
|
|
700
|
+
return {
|
|
701
|
+
sourceId: identifier(provenance.sourceId, "Provenance source id"),
|
|
702
|
+
sourceType: sourceType,
|
|
703
|
+
sourceName: text(provenance.sourceName, "Provenance source name", 120),
|
|
704
|
+
importedAt: timestamp(provenance.importedAt, "Provenance import time"),
|
|
705
|
+
importedBy: identifier(provenance.importedBy, "Provenance importer")
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
function validateFreshness(value) {
|
|
709
|
+
const freshness = object(value, "Record freshness is invalid.");
|
|
710
|
+
exactKeys(freshness, ["firstSeenAt", "lastSeenAt"], "record freshness");
|
|
711
|
+
return {
|
|
712
|
+
firstSeenAt: timestamp(freshness.firstSeenAt, "Record first seen time"),
|
|
713
|
+
lastSeenAt: timestamp(freshness.lastSeenAt, "Record last seen time")
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
export function buildRecordProvenance(source, importedAt, importedBy) {
|
|
717
|
+
return { sourceId: source.sourceId, sourceType: source.sourceType, sourceName: source.sourceName, importedAt, importedBy };
|
|
718
|
+
}
|
|
719
|
+
export function buildRecordFreshness(now) {
|
|
720
|
+
return { firstSeenAt: now, lastSeenAt: now };
|
|
721
|
+
}
|
|
722
|
+
export function edgeIdFor(sourceKind, sourceId, targetKind, targetId, relation) {
|
|
723
|
+
return `edge-${sha256Digest(`${sourceKind}:${sourceId}:${targetKind}:${targetId}:${relation}`).slice(0, 40)}`;
|
|
724
|
+
}
|
|
725
|
+
export function freshnessStatusFor(lastSeenAt, now, staleDays) {
|
|
726
|
+
const ageDays = (Date.parse(now) - Date.parse(lastSeenAt)) / (24 * 60 * 60 * 1000);
|
|
727
|
+
return ageDays <= staleDays ? "fresh" : "stale";
|
|
728
|
+
}
|
|
729
|
+
export function daysSince(thenIso, nowIso) {
|
|
730
|
+
return Math.max(0, Math.floor((Date.parse(nowIso) - Date.parse(thenIso)) / (24 * 60 * 60 * 1000)));
|
|
731
|
+
}
|
|
732
|
+
function object(value, message) {
|
|
733
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
734
|
+
throw new InventoryError(message);
|
|
735
|
+
return value;
|
|
736
|
+
}
|
|
737
|
+
function array(value, label, max) {
|
|
738
|
+
if (!Array.isArray(value) || value.length > max)
|
|
739
|
+
throw new InventoryError(`${label} is invalid.`);
|
|
740
|
+
return value;
|
|
741
|
+
}
|
|
742
|
+
function exactKeys(value, keys, label, optional = []) {
|
|
743
|
+
for (const key of Object.keys(value)) {
|
|
744
|
+
if (!keys.includes(key) || (value[key] === undefined && !optional.includes(key)))
|
|
745
|
+
throw new InventoryError(`${label} contains unsupported field: ${key}`);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
function unique(values, label) {
|
|
749
|
+
if (new Set(values).size !== values.length)
|
|
750
|
+
throw new InventoryError(`${label} must be unique.`);
|
|
751
|
+
}
|
|
752
|
+
function identifier(value, label) {
|
|
753
|
+
if (typeof value !== "string" || !IDENTIFIER.test(value) || sanitizeSecretString(value) !== value)
|
|
754
|
+
throw new InventoryError(`${label} must be a safe identifier.`);
|
|
755
|
+
return value;
|
|
756
|
+
}
|
|
757
|
+
function identifiers(value, label, max) {
|
|
758
|
+
return array(value, label, max).map((entry) => identifier(entry, label));
|
|
759
|
+
}
|
|
760
|
+
function scopes(value, label, max) {
|
|
761
|
+
return array(value, label, max).map((entry) => {
|
|
762
|
+
if (typeof entry !== "string" || !SCOPE_IDENTIFIER.test(entry) || sanitizeSecretString(entry) !== entry)
|
|
763
|
+
throw new InventoryError(`${label} must be safe scope identifiers.`);
|
|
764
|
+
return entry;
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
function text(value, label, max) {
|
|
768
|
+
if (typeof value !== "string" || value.trim() === "" || value.length > max || /[\u0000-\u001f]/.test(value) || sanitizeSecretString(value) !== value)
|
|
769
|
+
throw new InventoryError(`${label} is invalid.`);
|
|
770
|
+
return value.trim();
|
|
771
|
+
}
|
|
772
|
+
function timestamp(value, label) {
|
|
773
|
+
if (typeof value !== "string" || !TIMESTAMP.test(value) || !Number.isFinite(Date.parse(value)))
|
|
774
|
+
throw new InventoryError(`${label} must be an ISO UTC timestamp.`);
|
|
775
|
+
return value;
|
|
776
|
+
}
|
|
777
|
+
function hash(value, label) {
|
|
778
|
+
if (typeof value !== "string" || !HASH.test(value))
|
|
779
|
+
throw new InventoryError(`${label} must be a sha256 hash.`);
|
|
780
|
+
return value;
|
|
781
|
+
}
|
|
782
|
+
function nonNegative(value, label, max) {
|
|
783
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > max)
|
|
784
|
+
throw new InventoryError(`${label} is invalid.`);
|
|
785
|
+
return value;
|
|
786
|
+
}
|
|
787
|
+
//# sourceMappingURL=types.js.map
|