Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 18 additions & 0 deletions airbyte/mcp/_arg_resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,27 @@ def resolve_manifest_yaml(manifest_yaml: str | Path | None) -> str | Path | None
if is_trusted_execution_enabled():
return Path(manifest_yaml)

if _looks_like_file_path(manifest_yaml):
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
raise AirbyteLibInputError(
message="`manifest_yaml` looks like a file path, which this server cannot read.",
guidance=(
"This MCP server does not run on your machine and cannot read local files. "
"Pass the manifest content inline as the `manifest_yaml` value."
),
context={"manifest_yaml": manifest_yaml},
)

return manifest_yaml


def _looks_like_file_path(value: str) -> bool:
"""Return True for a single-line value shaped like a manifest file path."""
stripped = value.strip()
return stripped.lower().endswith((".yaml", ".yml", ".json")) or stripped.startswith(
("/", "~", "./", "../")
)


def resolve_connector_config( # noqa: PLR0912
config: dict | str | None = None,
config_file: str | Path | None = None,
Expand Down
7 changes: 4 additions & 3 deletions airbyte/mcp/cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -2991,8 +2991,8 @@ def publish_custom_source_definition(
Field(
description=(
"The Low-code CDK manifest as inline YAML or a file path. File paths are "
"honored only when trusted execution is enabled; otherwise, the value is "
"treated as inline YAML. Required for YAML connectors."
"honored only when trusted execution is enabled; otherwise, pass the manifest "
"content inline (path-like values are rejected). Required for YAML connectors."
),
default=None,
),
Expand Down Expand Up @@ -3234,7 +3234,8 @@ def update_custom_source_definition(
Field(
description=(
"New manifest as inline YAML or a file path. File paths are honored only when "
"trusted execution is enabled; otherwise, the value is treated as inline YAML. "
"trusted execution is enabled; otherwise, pass the manifest content inline "
"(path-like values are rejected). "
"Optional; omit to update only testing values."
),
default=None,
Expand Down
31 changes: 26 additions & 5 deletions tests/unit_tests/test_mcp_trusted_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ def test_resolve_manifest_yaml_rejects_path_when_untrusted(
@pytest.mark.parametrize(
"manifest_yaml",
[
pytest.param("manifest.yaml", id="single-line-text"),
pytest.param("not-a-manifest", id="single-line-text"),
pytest.param(
'{"version": "0.1.0", "type": "DeclarativeSource"}', id="inline-json"
),
Expand All @@ -196,6 +196,27 @@ def test_resolve_manifest_yaml_preserves_inline_values_when_untrusted(
assert resolved == manifest_yaml


@pytest.mark.parametrize(
"manifest_yaml",
[
pytest.param("manifest.yaml", id="relative-file"),
pytest.param("/private/tmp/session/bill_manifest.yaml", id="absolute-yaml"),
pytest.param("~/connectors/manifest.YML", id="home-yml"),
pytest.param("./manifest", id="dot-relative"),
],
)
def test_resolve_manifest_yaml_rejects_path_like_text_when_untrusted(
monkeypatch: MonkeyPatch,
manifest_yaml: str,
) -> None:
"""Untrusted execution rejects single-line path-like text and asks for inline content."""
_set_trusted(monkeypatch, enabled=False)
with pytest.raises(AirbyteLibInputError) as exc_info:
resolve_manifest_yaml(manifest_yaml)
assert exc_info.value.guidance is not None
assert "inline" in exc_info.value.guidance


def test_resolve_manifest_yaml_preserves_existing_path_when_trusted(
monkeypatch: MonkeyPatch,
tmp_path: Path,
Expand Down Expand Up @@ -228,13 +249,13 @@ def test_resolve_manifest_yaml_does_not_read_file_when_untrusted(
monkeypatch: MonkeyPatch,
tmp_path: Path,
) -> None:
"""A path string remains inline YAML when trusted execution is disabled."""
"""A path string is rejected, not read, when trusted execution is disabled."""
_set_trusted(monkeypatch, enabled=False)
manifest_path = tmp_path / "manifest.yaml"
manifest_path.write_text("version: 0.1.0\ntype: DeclarativeSource\n")
resolved = resolve_manifest_yaml(str(manifest_path))
assert isinstance(resolved, str)
assert resolved == str(manifest_path)
with pytest.raises(AirbyteLibInputError) as exc_info:
resolve_manifest_yaml(str(manifest_path))
assert "version: 0.1.0" not in str(exc_info.value)


_UNTRUSTED_LOCAL_HELPERS: list[Callable[[], object]] = [
Expand Down
Loading