Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
b3014db
fix: upgrade express security dependency chain
Joncallim Sep 2, 2026
4c12108
feat: add Docker runtime edge evidence refs
Joncallim Sep 1, 2026
08617a1
fix: attest Docker evidence with observation token
Joncallim Sep 1, 2026
d6a72cd
feat: expose runtime edge evidence inspector
Joncallim Sep 1, 2026
7202d08
docs: describe runtime evidence observation token accurately
Joncallim Sep 1, 2026
62e34eb
fix: harden version one runtime evidence
Joncallim Sep 1, 2026
41d53d5
fix: require explicit runtime evidence tokens
Joncallim Sep 1, 2026
8ad070c
fix: reject empty runtime evidence tokens
Joncallim Sep 1, 2026
2758383
fix: validate version one evidence semantics
Joncallim Sep 1, 2026
b65cd6a
fix: bind runtime evidence to edge endpoints
Joncallim Sep 1, 2026
3c03324
feat: evidence Docker Compose dependency declarations
Joncallim Sep 2, 2026
8a33379
feat: expose Compose dependency evidence
Joncallim Sep 2, 2026
5768820
test: cover Compose dependency evidence fixture
Joncallim Sep 2, 2026
81215b5
test: reject self-attested Compose evidence
Joncallim Sep 2, 2026
318512e
feat: schedule systemd observations independently
Joncallim Sep 2, 2026
82956fd
fix: align systemd provider state contract
Joncallim Sep 2, 2026
b8c9eff
test: preserve scheduler legacy baseline
Joncallim Sep 2, 2026
2f3c193
test: cover systemd provider state in hostile fixture
Joncallim Sep 2, 2026
368795f
feat: attest systemd dependency declarations
Joncallim Sep 2, 2026
0a50366
feat: validate systemd evidence boundary
Joncallim Sep 2, 2026
19492db
fix: preserve systemd dependency semantics
Joncallim Sep 2, 2026
01673f4
test: enforce systemd evidence semantics
Joncallim Sep 2, 2026
3028ff5
docs: explain systemd relationship evidence
Joncallim Sep 2, 2026
ce7e54c
feat: add bounded systemd findings endpoint
Joncallim Sep 2, 2026
e0d1c30
feat: expose bounded runtime findings
Joncallim Sep 2, 2026
7f6ec16
fix: attach canonical evidence to findings
Joncallim Sep 2, 2026
0bdf1bc
fix: bind findings to triggering evidence
Joncallim Sep 2, 2026
f832f76
test: cover findings accessibility
Joncallim Sep 2, 2026
5d4b187
test: cover 44-service map density
Joncallim Sep 2, 2026
1311c3d
feat: flag internal network port publications
Joncallim Sep 2, 2026
3c59f8b
feat: add internal network port findings
Joncallim Sep 2, 2026
6af0e71
fix: require host publication for internal port findings
Joncallim Sep 2, 2026
6dfbb8a
docs: define bounded Docker port finding
Joncallim Sep 2, 2026
9b9aa3e
docs: state findings severity policy
Joncallim Sep 2, 2026
a144be6
feat: derive path-free Docker daemon state evidence
Joncallim Sep 2, 2026
4e85038
feat: validate path-free Docker daemon evidence
Joncallim Sep 2, 2026
d9ff040
feat: flag Docker daemon state bind mounts
Joncallim Sep 2, 2026
2fdff82
feat: surface Docker daemon state findings
Joncallim Sep 2, 2026
1e8ff68
fix: suppress runtime evidence in mock mode
Joncallim Sep 2, 2026
7fa1c6e
feat: bind npm manifest provenance evidence
Joncallim Sep 2, 2026
ceec457
chore: refresh npm provenance contracts
Joncallim Sep 2, 2026
ebfe0a8
fix: accept closed npm provenance evidence
Joncallim Sep 2, 2026
a1ef1b9
fix: attest only bound Docker host ports
Joncallim Sep 2, 2026
9b0e928
docs: clarify runtime provenance boundaries
Joncallim Sep 2, 2026
c4da2fa
feat: bind cron schedule provenance to its own slot
Joncallim Sep 2, 2026
2240fea
feat: validate cron schedule provenance at API boundary
Joncallim Sep 2, 2026
c5e3063
feat: surface cron collection state
Joncallim Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,25 @@ jobs:
- name: Checkout
uses: actions/checkout@v6

# Issue #42: the runtime stage must mirror the lockfile's nested
# workspace layout (apps/api/node_modules) or the API cannot resolve
# express. Build the image on every PR so regressions ship nowhere.
# The runtime stage copies the pruned workspace dependency closure from
# the resolver's root. Build on every PR so hoisting or closure drift
# cannot ship unnoticed.
- name: Build image
run: docker build -t dockermap:ci .

- name: Assert runtime dependency boundary
run: |
set -euo pipefail
docker run --rm --entrypoint sh dockermap:ci -ec '
! command -v npm
! command -v npx
test ! -e /opt/dockermap/node_modules/.bin/tsx
test ! -e /opt/dockermap/node_modules/.bin/vite
test ! -d /opt/dockermap/node_modules/typescript
test ! -e /opt/dockermap/node_modules/@playwright/test/package.json
node -e "import(\"express\").then(() => import(\"@dockermap/contracts\"))"
'

- name: Smoke-test runtime image
run: |
set -euo pipefail
Expand Down
20 changes: 16 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ RUN npm run check:version && npm run check:contracts && npm run build
# Build and assert the entire package artifact, rather than relying on a
# source-tree module that happened to be copied into the image.
RUN test -f packages/contracts/dist/index.js && test -f packages/contracts/dist/nodeSchemas.js
# The runtime image needs the API's production dependency closure only. Prune
# after all builders have finished, so compiler/test tooling never crosses the
# runtime boundary. npm's workspace resolver may hoist that closure to the
# repository root, which is the only node_modules tree copied below.
RUN npm prune --omit=dev \
&& test ! -e node_modules/.bin/tsx && test ! -e node_modules/.bin/vite \
&& test ! -d node_modules/typescript && test ! -e node_modules/@playwright/test/package.json
Comment on lines +51 to +53

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope production pruning to the API workspace

In the checked Docker build, npm prune --help identifies -w|--workspace <workspace-name> as the workspace selector, but this root invocation supplies neither it nor a package. Consequently npm retains production dependencies for every workspace, including the web-only React and React Router packages, and the runtime stage copies them despite claiming an API-only closure; the CI checks only exclude selected development tools and do not detect these unused production modules.

Useful? React with 👍 / 👎.


# ---- Runtime image ----------------------------------------------------------
FROM node:22-bookworm-slim AS runtime
Expand All @@ -62,10 +69,6 @@ RUN groupadd --gid 10003 dockermap && \
WORKDIR /opt/dockermap

COPY --from=js-builder /src/node_modules ./node_modules
# npm nests workspace deps in the lockfile layout (apps/api/node_modules/express
# etc.); the runtime image must mirror that layout or the API cannot resolve
# its deps.
COPY --from=js-builder /src/apps/api/node_modules ./apps/api/node_modules
COPY --from=js-builder /src/package.json ./package.json
COPY --from=js-builder /src/apps/api/dist ./apps/api/dist
COPY --from=js-builder /src/apps/api/package.json ./apps/api/package.json
Expand All @@ -81,6 +84,15 @@ COPY deploy/docker/entrypoint.sh /entrypoint.sh
COPY deploy/docker/frontend-entrypoint.sh /frontend-entrypoint.sh
COPY deploy/docker/healthcheck.sh /usr/local/bin/dockermap-healthcheck
RUN chmod +x /entrypoint.sh /frontend-entrypoint.sh /usr/local/bin/dockermap-healthcheck
# The Node base image includes package-manager CLIs that DockerMap never uses
# at runtime. Remove them after staging the already-pruned closure; `node`
# remains available for the compiled API, while npm/npx cannot become an
# in-container mutation surface.
RUN rm -rf /usr/local/lib/node_modules/npm \
&& rm -f /usr/local/bin/npm /usr/local/bin/npx /usr/local/bin/corepack \
&& ! command -v npm && ! command -v npx \
&& test ! -e node_modules/.bin/tsx && test ! -e node_modules/.bin/vite \
&& test ! -d node_modules/typescript && test ! -e node_modules/@playwright/test/package.json

ENV NODE_ENV=production \
PORT=4000 \
Expand Down
4 changes: 2 additions & 2 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
"@dockermap/contracts": "0.1.0",
"ajv": "^8.20.0",
"cors": "^2.8.5",
"express": "^4.21.2",
"express": "^5.2.1",
"helmet": "^8.2.0"
},
"devDependencies": {
"@seriousme/openapi-schema-validator": "^2.9.1",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/express": "^5.0.6",
"tsx": "^4.20.5",
"typescript": "^5.9.2"
}
Expand Down
208 changes: 206 additions & 2 deletions apps/api/src/daemonResponseValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const DAEMON_RESPONSE_SCHEMA_PATHS = [
{ path: "/daemon/snapshot", routeId: "snapshot", schema: RUST_ROUTE_RESPONSE_SCHEMAS.snapshot },
{ path: "/daemon/graph", routeId: "graph", schema: RUST_ROUTE_RESPONSE_SCHEMAS.graph },
{ path: "/daemon/runtime/map", routeId: "runtime-map", schema: RUST_ROUTE_RESPONSE_SCHEMAS["runtime-map"] },
{ path: "/daemon/findings", routeId: "findings", schema: RUST_ROUTE_RESPONSE_SCHEMAS.findings },
{ path: "/daemon/containers", routeId: "containers", schema: RUST_ROUTE_RESPONSE_SCHEMAS.containers },
{ path: "/daemon/containers/:name", routeId: "container", schema: RUST_ROUTE_RESPONSE_SCHEMAS.container },
{ path: "/daemon/images", routeId: "images", schema: RUST_ROUTE_RESPONSE_SCHEMAS.images },
Expand All @@ -32,7 +33,7 @@ export const DAEMON_RESPONSE_SCHEMA_PATHS = [
{ path: "/daemon/compose/edit-plan", routeId: "compose-edit-plan", schema: RUST_ROUTE_RESPONSE_SCHEMAS["compose-edit-plan"] },
] as const satisfies readonly { path: string; schema: RustResponseSchemaId; routeId?: keyof typeof RUST_ROUTE_RESPONSE_SCHEMAS }[];

const ajv = new Ajv2020({ allErrors: true, strict: true, formats: { uint32: true, uint64: true } });
const ajv = new Ajv2020({ allErrors: true, strict: true, formats: { uint8: true, uint32: true, uint64: true } });
const validators = new Map<RustResponseSchemaId, ValidateFunction>(
(Object.entries(RUST_RESPONSE_SCHEMAS) as [RustResponseSchemaId, (typeof RUST_RESPONSE_SCHEMAS)[RustResponseSchemaId]][])
.map(([schema, definition]) => [schema, ajv.compile(definition)]),
Expand All @@ -45,12 +46,57 @@ const validators = new Map<RustResponseSchemaId, ValidateFunction>(
const PROVIDER_STATE_SLOT_SET = {
network_infrastructure: true,
host_scoped: true,
systemd: true,
python_processes: true,
native_processes: true,
project_npm: true,
cron: true,
} as const satisfies Record<ProviderSlot, true>;
const PROVIDER_STATE_SLOTS = Object.keys(PROVIDER_STATE_SLOT_SET) as ProviderSlot[];
const U32_MAX = 4_294_967_295;
const SYSTEMD_REQUIRES_FINDING_RULE = "systemd.requires_target_not_active";
const SYSTEMD_REQUIRES_FINDING_SUMMARY = "An active systemd service requires a target that is inactive or failed";
const SYSTEMD_REQUIRES_FINDING_RECOMMENDATION = "Inspect the target service state and its declared dependency configuration.";
const INTERNAL_NETWORK_PORT_FINDING_RULE = "docker.internal_network_member_publishes_port";
const INTERNAL_NETWORK_PORT_FINDING_SUMMARY = "A container on an internal Docker network also has a published host port.";
const INTERNAL_NETWORK_PORT_FINDING_RECOMMENDATION = "Review whether the host-port publication is intended for this internal-network service.";
const DOCKER_DAEMON_STATE_FINDING_RULE = "docker.daemon_state_bind_mount";
const DOCKER_DAEMON_STATE_FINDING_SUMMARY = "A container has Docker daemon state access that may provide Docker daemon API authority.";
const DOCKER_DAEMON_STATE_FINDING_RECOMMENDATION = "Review whether this container requires Docker daemon API authority.";

// Version-one evidence is intentionally a discriminated Docker observation,
// not a generic provenance bag. JSON Schema owns each field's closed enum;
// this small cross-field table binds an emitted fact to the relationship it
// can actually support. A later evidence version must add an explicit row.
const V1_EVIDENCE_EDGE = {
docker_network_membership: { relationship: "connected_to", sourcePrefix: "docker_container_", targetPrefix: "docker_network_" },
docker_volume_mount: { relationship: "mounts", sourcePrefix: "docker_container_", targetPrefix: "docker_volume_" },
docker_port_publication: { relationship: "exposes", sourcePrefix: "docker_container_", targetPrefix: "network_listener_" },
docker_compose_depends_on: { relationship: "depends_on", sourcePrefix: "docker_container_", targetPrefix: "docker_container_" },
docker_daemon_state_bind_mount: { relationship: "exposes_daemon_state", sourcePrefix: "docker_container_", targetPrefix: "host_risk_docker_daemon_state" },
} as const;

// Version two is the intentionally narrow systemd declaration vocabulary.
// It is tied to Systemd's independently scheduled slot, rather than to the
// broader host collection, so retained freshness stays attributable.
const V2_EVIDENCE_EDGE = {
systemd_requires: { relationship: "requires", sourcePrefix: "systemd_service_", targetPrefix: "systemd_service_" },
systemd_wants: { relationship: "wants", sourcePrefix: "systemd_service_", targetPrefix: "systemd_service_" },
systemd_part_of: { relationship: "part_of", sourcePrefix: "systemd_service_", targetPrefix: "systemd_service_" },
} as const;

// Version three is equally narrow: a package manifest declaration from the
// separately scheduled ProjectNpm slot. It says nothing about installation,
// resolution, execution, or package safety.
const V3_EVIDENCE_EDGE = {
npm_package_manifest_dependency: { relationship: "depends_on", sourcePrefix: "npm_project_", targetPrefix: "npm_package_" },
} as const;

// Version four is a parsed cron declaration from Cron's own scheduler slot.
// It makes no execution, successful-run, or host-health claim.
const V4_EVIDENCE_EDGE = {
cron_schedule_declaration: { relationship: "runs_on", sourcePrefix: "scheduled_job_", targetPrefix: "host_", target: "host_local" },
} as const;

function hasCompleteProviderStateVector(payload: unknown): boolean {
if (!payload || typeof payload !== "object") return false;
Expand Down Expand Up @@ -119,6 +165,162 @@ function hasCoherentProviderFreshness(payload: unknown): boolean {
});
}

function hasCoherentRuntimeEvidence(payload: unknown): boolean {
if (!payload || typeof payload !== "object") return false;
const edges = (payload as { edges?: unknown }).edges;
if (!Array.isArray(edges)) return false;
return edges.every((edge) => {
if (!edge || typeof edge !== "object") return false;
const candidate = edge as { source?: unknown; target?: unknown; relationship?: unknown; evidenceRefs?: unknown };
if (!Array.isArray(candidate.evidenceRefs)) return false;
return candidate.evidenceRefs.every((evidence) => {
if (!evidence || typeof evidence !== "object") return false;
const value = evidence as {
version?: unknown; provider?: unknown; kind?: unknown; assertionKind?: unknown;
freshness?: unknown; providerRevision?: unknown; collectedAt?: unknown; subjectRef?: unknown;
providerSlot?: unknown;
};
const isV1 = value.version === 1
&& value.provider === "docker"
&& value.assertionKind === "observed"
&& value.freshness === "fresh"
&& (value.providerSlot === null || value.providerSlot === undefined);
const isV2 = value.version === 2
&& value.provider === "systemd"
&& value.assertionKind === "declared"
&& value.providerSlot === "systemd"
&& (value.freshness === "fresh" || value.freshness === "stale" || value.freshness === "timed_out");
const isV3 = value.version === 3
&& value.provider === "npm"
&& value.assertionKind === "declared"
&& value.providerSlot === "project_npm"
&& (value.freshness === "fresh" || value.freshness === "stale" || value.freshness === "timed_out");
const isV4 = value.version === 4
&& value.provider === "cron"
&& value.assertionKind === "declared"
&& value.providerSlot === "cron"
&& (value.freshness === "fresh" || value.freshness === "stale" || value.freshness === "timed_out");
if (!isV1 && !isV2 && !isV3 && !isV4) return false;
const expected = typeof value.kind === "string"
? (isV1
? V1_EVIDENCE_EDGE[value.kind as keyof typeof V1_EVIDENCE_EDGE]
: isV2
? V2_EVIDENCE_EDGE[value.kind as keyof typeof V2_EVIDENCE_EDGE]
: isV3
? V3_EVIDENCE_EDGE[value.kind as keyof typeof V3_EVIDENCE_EDGE]
: V4_EVIDENCE_EDGE[value.kind as keyof typeof V4_EVIDENCE_EDGE])
: undefined;
if (!expected || candidate.relationship !== expected.relationship || typeof candidate.source !== "string" || typeof candidate.target !== "string") return false;
if (value.subjectRef !== candidate.source || !candidate.source.startsWith(expected.sourcePrefix) || !candidate.target.startsWith(expected.targetPrefix)) return false;
if (isV4 && candidate.target !== "host_local") return false;
if (value.kind === "docker_daemon_state_bind_mount" && candidate.target !== "host_risk_docker_daemon_state") return false;
if (candidate.source === candidate.target) return false;
// An opaque observation token must never be the collection timestamp
// re-labelled as a revision. The daemon produces it independently.
return typeof value.providerRevision === "string" && value.providerRevision !== String(value.collectedAt);
Comment on lines +218 to +220

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject noncanonical runtime evidence summaries

When the daemon is malformed or compromised, hasCoherentRuntimeEvidence accepts any schema-valid summary instead of the fixed summary associated with each evidence kind. A raw unit, process, or package-auth secret that does not match publishApiPayload's limited marker heuristics can therefore pass /api/runtime/map validation and be rendered by the evidence inspector; bind each kind to the canonical summaries already emitted by the Rust producers.

AGENTS.md reference: AGENTS.md:L17-L20

Useful? React with 👍 / 👎.

});
});
}

// Findings are a deliberately tiny conclusion vocabulary, not a daemon-supplied
// diagnostics channel. The generated schema owns field shape; this exact rule
// table prevents a compromised daemon from inventing mutable claims or copying
// arbitrary strings through the new endpoint.
function hasCoherentFindings(payload: unknown): boolean {
if (!payload || typeof payload !== "object") return false;
const findings = (payload as { findings?: unknown }).findings;
if (!Array.isArray(findings)) return false;
return findings.every((candidate) => {
if (!candidate || typeof candidate !== "object") return false;
const finding = candidate as Record<string, unknown>;
if (finding.ruleId === SYSTEMD_REQUIRES_FINDING_RULE) return finding.severity === "warning"
&& finding.summary === SYSTEMD_REQUIRES_FINDING_SUMMARY
&& finding.recommendation === SYSTEMD_REQUIRES_FINDING_RECOMMENDATION
&& typeof finding.id === "string"
&& finding.id.startsWith("finding_systemd_requires_target_not_active_")
&& typeof finding.subjectRef === "string"
&& finding.subjectRef.startsWith("systemd_service_")
&& typeof finding.targetRef === "string"
&& finding.targetRef.startsWith("systemd_service_")
&& finding.subjectRef !== finding.targetRef
&& Array.isArray(finding.evidenceRefs)
&& finding.evidenceRefs.length === 1
&& (() => {
const candidateEvidence = finding.evidenceRefs[0];
if (!candidateEvidence || typeof candidateEvidence !== "object") return false;
const evidence = candidateEvidence as Record<string, unknown>;
return evidence.version === 2
&& evidence.provider === "systemd"
&& evidence.kind === "systemd_requires"
&& evidence.assertionKind === "declared"
&& evidence.providerSlot === "systemd"
&& evidence.freshness === "fresh"
&& evidence.subjectRef === finding.subjectRef;
})();
if (finding.ruleId === DOCKER_DAEMON_STATE_FINDING_RULE) return finding.severity === "warning"
&& finding.summary === DOCKER_DAEMON_STATE_FINDING_SUMMARY
&& finding.recommendation === DOCKER_DAEMON_STATE_FINDING_RECOMMENDATION
&& typeof finding.id === "string"
&& finding.id.startsWith("finding_docker_daemon_state_bind_mount_")
&& typeof finding.subjectRef === "string"
&& finding.subjectRef.startsWith("docker_container_")
&& finding.targetRef === "host_risk_docker_daemon_state"
&& Array.isArray(finding.evidenceRefs)
&& finding.evidenceRefs.length === 1
&& (() => {
const candidateEvidence = finding.evidenceRefs[0];
if (!candidateEvidence || typeof candidateEvidence !== "object") return false;
const evidence = candidateEvidence as Record<string, unknown>;
return evidence.version === 1
&& evidence.provider === "docker"
&& evidence.kind === "docker_daemon_state_bind_mount"
&& evidence.assertionKind === "observed"
&& evidence.summary === "Docker reported a bind mount exposing Docker daemon state"
&& evidence.subjectRef === finding.subjectRef
&& evidence.providerSlot === null
&& evidence.freshness === "fresh"
&& typeof evidence.providerRevision === "string"
&& evidence.providerRevision !== String(evidence.collectedAt);
})();
if (finding.ruleId !== INTERNAL_NETWORK_PORT_FINDING_RULE) return false;
return finding.severity === "advisory"
&& finding.summary === INTERNAL_NETWORK_PORT_FINDING_SUMMARY
&& finding.recommendation === INTERNAL_NETWORK_PORT_FINDING_RECOMMENDATION
&& typeof finding.id === "string"
&& finding.id.startsWith("finding_docker_internal_network_member_publishes_port_")
&& typeof finding.subjectRef === "string"
&& finding.subjectRef.startsWith("docker_container_")
&& typeof finding.targetRef === "string"
&& finding.targetRef.startsWith("docker_network_")
&& Array.isArray(finding.evidenceRefs)
&& finding.evidenceRefs.length === 2
&& (() => {
const [membership, port] = finding.evidenceRefs;
if (!membership || typeof membership !== "object" || !port || typeof port !== "object") return false;
const networkEvidence = membership as Record<string, unknown>;
const portEvidence = port as Record<string, unknown>;
return networkEvidence.version === 1
&& networkEvidence.provider === "docker"
&& networkEvidence.kind === "docker_network_membership"
&& networkEvidence.assertionKind === "observed"
&& networkEvidence.freshness === "fresh"
&& networkEvidence.providerSlot === null
&& networkEvidence.subjectRef === finding.subjectRef
&& typeof networkEvidence.providerRevision === "string"
&& networkEvidence.providerRevision !== String(networkEvidence.collectedAt)
&& portEvidence.version === 1
&& portEvidence.provider === "docker"
&& portEvidence.kind === "docker_port_publication"
&& portEvidence.assertionKind === "observed"
&& portEvidence.freshness === "fresh"
&& portEvidence.providerSlot === null
&& portEvidence.subjectRef === finding.subjectRef
&& typeof portEvidence.providerRevision === "string"
&& portEvidence.providerRevision !== String(portEvidence.collectedAt);
})();
});
}

export function daemonResponseSchemaId(path: string): RustResponseSchemaId | undefined {
const pathname = path.split("?", 1)[0];
if (pathname === "/daemon/containers") return "ContainersResponse";
Expand Down Expand Up @@ -150,7 +352,9 @@ export class DaemonResponseValidationError extends Error {
export function validateDaemonResponse(path: string, payload: unknown) {
const schema = daemonResponseSchemaId(path);
const validator = schema && validators.get(schema);
if (!validator || !validator(payload) || (schema === "RuntimeMap" && (!hasCompleteProviderStateVector(payload) || !hasCoherentProviderFreshness(payload)))) {
if (!validator || !validator(payload)
|| (schema === "RuntimeMap" && (!hasCompleteProviderStateVector(payload) || !hasCoherentProviderFreshness(payload) || !hasCoherentRuntimeEvidence(payload)))
|| (schema === "FindingsResponse" && !hasCoherentFindings(payload))) {
throw new DaemonResponseValidationError();
}
return payload;
Expand Down
Loading
Loading