Skip to content

data_boundary_audit

data_boundary_audit

Application data-boundary diagnostics for OpenJarvis.

The report builder intentionally limits itself to configuration values, environment-key presence, and file existence. It never reads private user content from connector credential files, memory files, trace databases, logs, or other local runtime stores.

Classes

DataBoundaryFinding dataclass

DataBoundaryFinding(id: str, status: Status, title: str, potential_data_path: str, evidence: str, recommendation: str, location: str = '', absolute_location: str = '')

One application data-boundary finding.

Functions
to_dict
to_dict(*, show_paths: bool = False) -> dict[str, str]

Return a stable JSON-serializable representation.

Absolute paths and connector basenames are redacted by default. When show_paths is true, the location field exposes the absolute path for local debugging.

Source code in src/openjarvis/security/data_boundary_audit.py
def to_dict(self, *, show_paths: bool = False) -> dict[str, str]:
    """Return a stable JSON-serializable representation.

    Absolute paths and connector basenames are redacted by default. When
    ``show_paths`` is true, the location field exposes the absolute path for
    local debugging.
    """
    payload = {
        "id": self.id,
        "status": self.status,
        "title": self.title,
        "potential_data_path": self.potential_data_path,
        "evidence": self.evidence,
        "recommendation": self.recommendation,
    }
    if self.absolute_location:
        payload["location"] = (
            self.absolute_location if show_paths else self.location
        )
    elif self.location:
        payload["location"] = self.location
    return payload

DataBoundaryReport dataclass

DataBoundaryReport(verdict: str, root: str, config_loaded: bool, findings: tuple[DataBoundaryFinding, ...])

Structured result returned by the data-boundary audit.

Functions
summary
summary() -> dict[str, int]

Count findings by status.

Source code in src/openjarvis/security/data_boundary_audit.py
def summary(self) -> dict[str, int]:
    """Count findings by status."""
    counts = {"fail": 0, "warn": 0, "info": 0}
    for finding in self.findings:
        counts[finding.status] += 1
    return counts
to_dict
to_dict(*, show_paths: bool = False) -> dict[str, Any]

Return a stable JSON-serializable representation.

Source code in src/openjarvis/security/data_boundary_audit.py
def to_dict(self, *, show_paths: bool = False) -> dict[str, Any]:
    """Return a stable JSON-serializable representation."""
    return {
        "schema_version": 1,
        "verdict": self.verdict,
        "root": self.root if show_paths else _redact_root(self.root),
        "config_loaded": self.config_loaded,
        "summary": self.summary(),
        "findings": [
            finding.to_dict(show_paths=show_paths) for finding in self.findings
        ],
    }

Functions

build_data_boundary_report

build_data_boundary_report(config: Any, root: Path | None, *, config_loaded: bool = True, config_error: str = '', root_error: str = '') -> DataBoundaryReport

Build a data-boundary report from config and runtime paths.

The function is side-effect-free. It uses defensive getattr access so the command remains robust across configuration additions.

Source code in src/openjarvis/security/data_boundary_audit.py
def build_data_boundary_report(
    config: Any,
    root: Path | None,
    *,
    config_loaded: bool = True,
    config_error: str = "",
    root_error: str = "",
) -> DataBoundaryReport:
    """Build a data-boundary report from config and runtime paths.

    The function is side-effect-free. It uses defensive ``getattr`` access so
    the command remains robust across configuration additions.
    """

    builder = _FindingBuilder()
    root_path: Path | None = None
    root_label = "<unresolved-openjarvis-home>"
    if root is not None:
        try:
            root_path = root.expanduser().resolve()
            root_label = str(root_path)
        except Exception as exc:  # pragma: no cover - defensive path handling
            root_error = f"{type(exc).__name__}: {exc}"

    if root_error:
        error_type = str(root_error).partition(":")[0].strip() or "Error"
        if not error_type.replace("_", "").isalnum():
            error_type = "Error"
        builder.add(
            finding_id="config-root-error",
            status="fail",
            title="OpenJarvis home directory could not be resolved",
            potential_data_path="runtime state root -> local store checks",
            evidence=(
                f"{error_type} while resolving OpenJarvis home; "
                "path details were redacted"
            ),
            recommendation=(
                "Fix OPENJARVIS_HOME or XDG_DATA_HOME before relying on "
                "local-store data-boundary checks."
            ),
        )

    if config_error:
        builder.add(
            finding_id="config-load-error",
            status="fail",
            title="OpenJarvis config could not be loaded",
            potential_data_path="config.toml -> data-boundary report",
            evidence=_truncate(config_error),
            recommendation=(
                "Fix the configuration file before relying on config-derived "
                "data-boundary checks. Local store and environment checks still run."
            ),
        )
    elif config_loaded:
        _audit_outbound_settings(config, builder)
        _audit_telemetry_settings(config, builder)
        _audit_memory_cloud_composition(config, builder)
        _audit_memory_service(config, builder)
        _audit_deep_research_settings(config, builder)
        _audit_trace_and_learning_settings(config, builder)
        _audit_tool_surfaces(config, builder)
        _audit_server_exposure(config, builder)
        _audit_channel_settings(config, builder)
        _audit_skills_and_digest(config, builder)
        _audit_speech_settings(config, builder)
    elif not root_error:
        builder.add(
            finding_id="config-file-missing",
            status="info",
            title="OpenJarvis config file was not found",
            potential_data_path="configuration defaults -> data-boundary report",
            evidence="config_loaded = false",
            recommendation=(
                "Run `jarvis init` before relying on config-derived checks. "
                "Local store and environment checks still run."
            ),
        )

    active_tools = (
        _configured_tools(config) if config_loaded and not config_error else set()
    )
    active_config = config if config_loaded and not config_error else None
    if active_config is not None:
        _audit_security_settings(
            active_config,
            builder,
            has_external_surface=_has_external_surface(active_config, active_tools),
        )

    if root_path is not None:
        _audit_local_stores(root_path, builder, config=active_config)
        _audit_connector_credentials(root_path, builder)
        if active_config is not None:
            _audit_local_channel_credential_dirs(active_config, root_path, builder)
            _audit_knowledge_cloud_composition(active_config, root_path, builder)
    _audit_environment_credentials(
        active_config,
        builder,
        active_tools=active_tools,
    )
    _audit_channel_environment_credentials(active_config, builder)
    _audit_generic_runtime_credentials(builder)
    if _has_cloud_api_surface(active_config, active_tools):
        _audit_frontend_storage_scope(builder)

    findings = builder.build()
    return DataBoundaryReport(
        verdict=_derive_verdict(findings),
        root=root_label,
        config_loaded=config_loaded and not bool(config_error),
        findings=findings,
    )