Compare commits

...
Author SHA1 Message Date
John KennedyandGitHub cabaaf9a73 Merge branch 'main' into open-swe/jkb-55-safe-git-urls 2026-08-20 00:07:16 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f09cfe8ffc chore(deps): bump langgraph-checkpoint-postgres from 3.0.5 to 3.1.1 in /libs/cli/uv-examples/monorepo in the uv group across 1 directory (#8646)
Bumps the uv group with 1 update in the /libs/cli/uv-examples/monorepo
directory:
[langgraph-checkpoint-postgres](https://github.com/langchain-ai/langgraph).

Updates `langgraph-checkpoint-postgres` from 3.0.5 to 3.1.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langgraph/releases">langgraph-checkpoint-postgres's
releases</a>.</em></p>
<blockquote>
<h2>langgraph-checkpoint-postgres==3.1.1</h2>
<p>Changes since checkpointpostgres==3.1.0</p>
<ul>
<li>release(checkpoint-postgres): 3.1.1 (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8480">#8480</a>)</li>
<li>fix(checkpoint-postgres,checkpoint-sqlite): scope namespace matching
to segment boundaries (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8478">#8478</a>)</li>
<li>feat(checkpoint,checkpoint-postgres): add opt-in omit_expired to
skip expired rows on read (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8354">#8354</a>)</li>
<li>chore(deps): bump the minor-and-patch group in
/libs/checkpoint-postgres with 5 updates (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8250">#8250</a>)</li>
<li>chore(deps): bump langsmith from 0.8.0 to 0.8.18 in
/libs/checkpoint-postgres (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8171">#8171</a>)</li>
<li>docs: standardize package <code>README.md</code> structure (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8064">#8064</a>)</li>
<li>chore: migrate Python type checking to ty (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8002">#8002</a>)</li>
<li>chore(deps): bump the minor-and-patch group in
/libs/checkpoint-postgres with 7 updates (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7965">#7965</a>)</li>
<li>release(checkpoint): 4.1.1 (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7890">#7890</a>)</li>
<li>chore(deps): bump idna from 3.11 to 3.15 in
/libs/checkpoint-postgres (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7861">#7861</a>)</li>
<li>chore(deps): bump langsmith from 0.7.31 to 0.8.0 in
/libs/checkpoint-postgres (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7785">#7785</a>)</li>
</ul>
<h2>langgraph-checkpoint-sqlite==3.1.1</h2>
<p>Changes since checkpointsqlite==3.1.0</p>
<ul>
<li>release(checkpoint-sqlite): 3.1.1 (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8481">#8481</a>)</li>
<li>fix(checkpoint-postgres,checkpoint-sqlite): scope namespace matching
to segment boundaries (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8478">#8478</a>)</li>
<li>chore(deps): bump the minor-and-patch group in
/libs/checkpoint-sqlite with 4 updates (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8249">#8249</a>)</li>
<li>chore(deps): bump langsmith from 0.8.0 to 0.8.18 in
/libs/checkpoint-sqlite (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8177">#8177</a>)</li>
<li>docs: standardize package <code>README.md</code> structure (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8064">#8064</a>)</li>
<li>chore: migrate Python type checking to ty (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8002">#8002</a>)</li>
<li>chore(deps): bump the minor-and-patch group in
/libs/checkpoint-sqlite with 3 updates (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7961">#7961</a>)</li>
<li>release(checkpoint): 4.1.1 (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7890">#7890</a>)</li>
<li>chore(deps): bump langsmith from 0.7.31 to 0.8.0 in
/libs/checkpoint-sqlite (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7786">#7786</a>)</li>
<li>chore(deps): bump idna from 3.11 to 3.15 in /libs/checkpoint-sqlite
(<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7862">#7862</a>)</li>
</ul>
<h2>langgraph-checkpoint-postgres==3.1.0</h2>
<p>Changes since checkpointpostgres==3.1.0a4</p>
<ul>
<li>release: bump alpha packages to official versions (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7775">#7775</a>)</li>
<li>chore(deps): bump urllib3 from 2.6.3 to 2.7.0 in
/libs/checkpoint-postgres (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7761">#7761</a>)</li>
<li>chore(deps): bump langchain-core from 1.3.2 to 1.3.3 in
/libs/checkpoint-postgres (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7754">#7754</a>)</li>
<li>fix(checkpoint-postgres): add column aliases to seed-blob branch of
delta stage-2 UNION ALL (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7728">#7728</a>)</li>
</ul>
<h2>langgraph-checkpoint-sqlite==3.1.0</h2>
<p>Changes since checkpointsqlite==3.1.0a1</p>
<ul>
<li>release: bump alpha packages to official versions (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7775">#7775</a>)</li>
<li>chore(deps): bump urllib3 from 2.6.3 to 2.7.0 in
/libs/checkpoint-sqlite (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7760">#7760</a>)</li>
<li>chore(deps): bump langchain-core from 1.2.28 to 1.3.3 in
/libs/checkpoint-sqlite (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7751">#7751</a>)</li>
<li>chore: remove keepset helper (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7745">#7745</a>)</li>
<li>chore(langgraph): add guide/conformance for delta channel
checkpointer (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7736">#7736</a>)</li>
</ul>
<h2>langgraph-checkpoint-postgres==3.1.0a4</h2>
<p>Changes since checkpointpostgres==3.1.0a3</p>
<ul>
<li>release: alpha bump (a4) for langgraph, checkpoint,
checkpoint-postgres (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7701">#7701</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/b2926a0ff9589c28c7e01fe7cdbb337b86d5a4b4"><code>b2926a0</code></a>
release(checkpoint-sqlite): 3.1.1 (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8481">#8481</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/fcdf520938469c8e0992ca2075d6a9582c33260f"><code>fcdf520</code></a>
release(checkpoint-postgres): 3.1.1 (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8480">#8480</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/66ebe1a0da921e73f0f9f879ba105d314c079f7c"><code>66ebe1a</code></a>
fix(checkpoint-postgres,checkpoint-sqlite): scope namespace matching to
segme...</li>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/41341457342327166d72fc11952ab28fb61ec0bf"><code>4134145</code></a>
release(langgraph): 1.2.10 (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8462">#8462</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/30c4d58db86455128e42ddec96b1ba53c553ba22"><code>30c4d58</code></a>
chore(deps): bump jupyterlab from 4.5.9 to 4.5.10 in /libs/langgraph (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8440">#8440</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/1f2f88b2b74aa4e26c6a8be877dcb95bad2cef48"><code>1f2f88b</code></a>
chore(deps): bump js-yaml from 4.2.0 to 4.3.0 in
/libs/cli/js-monorepo-exampl...</li>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/270820363d56cf8a8e594d2d3b19d543230337fb"><code>2708203</code></a>
chore(deps): bump setuptools from 82.0.1 to 83.0.0 in /libs/cli (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8434">#8434</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/9f1e40bfeea28dee2155b4a57f5babd0a53534cf"><code>9f1e40b</code></a>
chore(deps): bump setuptools from 80.9.0 to 83.0.0 in /libs/langgraph
(<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8435">#8435</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/1e1ca88dad9c7e708263257fa9fc27a3fbdfff68"><code>1e1ca88</code></a>
feat(langgraph): type v3 stream_events return and native projections (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8389">#8389</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/31f90df3e6b0268fa77fd2d118a917d420b84a68"><code>31f90df</code></a>
revert(langgraph): delete TracePolicy (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/8403">#8403</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langgraph/compare/checkpointpostgres==3.0.5...checkpointsqlite==3.1.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langgraph-checkpoint-postgres&package-manager=uv&previous-version=3.0.5&new-version=3.1.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-20 07:02:48 +00:00
John KennedyandGitHub 7c2181f1f3 Merge branch 'main' into open-swe/jkb-55-safe-git-urls 2026-08-19 17:04:10 -07:00
John Kennedyandopen-swe[bot] <open-swe@users.noreply.github.com> 0cce2d2f2b fix(cli): report config source for invalid Git URLs
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-08-19 23:57:21 +00:00
John Kennedy 62ecd5414e fix(cli): stabilize generated schemas 2026-08-11 10:00:27 -07:00
John Kennedy 48ecb3bc81 fix(cli): address Git dependency review feedback 2026-08-11 09:40:13 -07:00
John Kennedyandopen-swe[bot] <open-swe@users.noreply.github.com> b51ef57707 fix: validate nested Git dependency credentials
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-08-05 22:36:53 +00:00
John Kennedyandopen-swe[bot] <open-swe@users.noreply.github.com> de9b5216c8 fix: reject credential-bearing Git dependencies
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-08-05 22:15:35 +00:00
8 changed files with 380 additions and 14 deletions
+2
View File
@@ -103,6 +103,8 @@ The CLI uses a `langgraph.json` configuration file with these key settings:
}
```
Git dependencies should use credential-free URLs. The CLI conservatively scans direct `langgraph.json` dependencies, common Python package files, uv project and lock files, and common Node.js package and lock files for HTTP Git URLs with userinfo. This check is not exhaustive: generated Docker builds can copy other files, including nested requirement or constraint files, into image layers without scanning them. For private dependencies, provide short-lived credentials through your build environment's secret-backed Git credential helper. Do not store credentials in copied files such as `langgraph.json` or `pip_config_file`.
See the [full documentation](https://reference.langchain.com/python/langgraph-cli) for detailed configuration options.
## Development
+87 -3
View File
@@ -6,6 +6,7 @@ import re
import shlex
import textwrap
from collections import Counter
from collections.abc import Iterable
from typing import Literal, NamedTuple
import click
@@ -36,6 +37,10 @@ DISALLOWED_BUILD_COMMAND_CHARS = [
# This blocks background execution (cmd &) while allowing command
# chaining (cmd1 && cmd2) which is common in build commands.
_SINGLE_AMPERSAND_RE = re.compile(r"(?<!&)&(?:&&)*(?!&)")
_GIT_HTTP_AUTHORITY_RES = (
re.compile(r"git\+https?://(?P<authority>[^/\s\"']+)", re.I),
re.compile(r"\bgit\s*=\s*[\"']https?://(?P<authority>[^/\s\"']+)", re.I),
)
_API_VERSION_PATTERN = re.compile(
r"^(?P<major>\d+)"
r"(?:\.(?P<minor>\d+))?"
@@ -78,6 +83,62 @@ def has_disallowed_build_command_content(command: str) -> bool:
return False
def _has_git_http_url_userinfo(dependency: str) -> bool:
"""Check whether a Git HTTP URL contains userinfo."""
return any(
"@" in match.group("authority")
for pattern in _GIT_HTTP_AUTHORITY_RES
for match in pattern.finditer(dependency)
)
def _validate_git_http_url_userinfo(
values: Iterable[str], *, source: pathlib.Path | None = None
) -> None:
"""Reject credential-bearing Git HTTP URLs without echoing their values."""
if not any(_has_git_http_url_userinfo(value) for value in values):
return
message = (
"Git dependency URLs must not contain credentials or other URL "
"userinfo because generated Dockerfiles and image layers can retain "
"them. Use a credential-free Git URL and provide short-lived "
"credentials through your build environment's secret-backed Git "
"credential helper."
)
if source is not None:
message += f" Found in: {source}"
raise click.UsageError(message)
def _validate_git_http_url_userinfo_files(paths: Iterable[pathlib.Path]) -> None:
"""Reject credential-bearing Git HTTP URLs in dependency files."""
for path in paths:
path = path.resolve()
if not path.is_file():
continue
try:
contents = path.read_text(encoding="utf-8", errors="replace")
except OSError:
raise click.UsageError(
f"Could not inspect dependency file for embedded credentials: {path}"
) from None
_validate_git_http_url_userinfo([contents], source=path)
def _validate_local_dependency_files(config_path: pathlib.Path, config: Config) -> None:
"""Validate dependency files copied into a non-uv Python image."""
paths: list[pathlib.Path] = []
for dependency in config["dependencies"]:
if not isinstance(dependency, str) or not dependency.startswith("."):
continue
root = (config_path.parent / dependency).resolve()
paths.extend(
root / name
for name in ("requirements.txt", "pyproject.toml", "setup.py", "setup.cfg")
)
_validate_git_http_url_userinfo_files(paths)
MIN_PYTHON_VERSION = "3.11"
DEFAULT_PYTHON_VERSION = "3.11"
@@ -320,7 +381,9 @@ def _get_source_kind(config: Config) -> str | None:
return kind if isinstance(kind, str) else None
def validate_config(config: Config) -> Config:
def validate_config(
config: Config, *, source_path: pathlib.Path | None = None
) -> Config:
"""Validate a configuration dictionary."""
graphs = config.get("graphs", {})
@@ -415,6 +478,15 @@ def validate_config(config: Config) -> Config:
' "source": {"kind": "uv", "root": ".."}'
)
_validate_git_http_url_userinfo(
(
dependency
for dependency in config["dependencies"]
if isinstance(dependency, str)
),
source=source_path,
)
source = config.get("source")
source_kind = _get_source_kind(config)
if source is not None and not isinstance(source, dict):
@@ -609,7 +681,7 @@ def validate_config_file(config_path: pathlib.Path) -> Config:
"""Load and validate a configuration file."""
with open(config_path) as f:
config = json.load(f)
validated = validate_config(config)
validated = validate_config(config, source_path=config_path.resolve())
# Enforce the package.json doesn't enforce an
# incompatible Node.js version
if validated.get("node_version"):
@@ -1280,6 +1352,7 @@ def python_config_to_docker(
api_version=api_version,
build_tools_to_uninstall=build_tools_to_uninstall,
)
_validate_local_dependency_files(config_path, config)
if pip_installer == "auto":
if _image_supports_uv(base_image):
pip_installer = "uv"
@@ -1490,7 +1563,18 @@ def node_config_to_docker(
) -> tuple[str, dict[str, str]]:
# Calculate paths for monorepo support
install_root = (
pathlib.Path(build_context).resolve() if build_context else config_path.parent
pathlib.Path(build_context).resolve()
if build_context
else config_path.parent.resolve()
)
config_root = config_path.parent.resolve()
dependency_roots = (
(install_root, config_root) if install_root != config_root else (install_root,)
)
_validate_git_http_url_userinfo_files(
root / name
for root in dependency_roots
for name in ("package.json", "package-lock.json", "yarn.lock", "pnpm-lock.yaml")
)
install_cmd = install_command or _get_node_pm_install_cmd(install_root)
if build_context:
+5 -1
View File
@@ -650,7 +650,8 @@ class Config(TypedDict, total=False):
pip_config_file: str | None
"""Optional. Path to a pip config file (e.g., "/etc/pip.conf" or "pip.ini") for controlling
package installation (custom indices, credentials, etc.).
package installation (custom indices, timeouts, etc.). The file is copied into the
generated image, so it must not contain credentials or other secrets.
Only relevant if Python dependencies are installed via pip. If omitted, default pip settings are used.
"""
@@ -689,6 +690,9 @@ class Config(TypedDict, total=False):
- "." or "./src" if you have a local Python package
- str (aka "anthropic") for a PyPI package
- "git+https://github.com/org/repo.git@main" for a Git-based package
Git HTTP URLs must not contain userinfo such as a username or token. For private
dependencies, provide short-lived credentials through the build environment's
secret-backed Git credential helper.
Defaults to an empty list, meaning no additional packages installed beyond your base environment.
This field is not supported when `source.kind` is `uv`.
+26
View File
@@ -880,6 +880,7 @@ def python_config_to_docker_uv_lock(
_get_node_pm_install_cmd,
_get_pip_cleanup_lines,
_image_supports_uv,
_validate_git_http_url_userinfo_files,
docker_tag,
)
@@ -890,11 +891,20 @@ def python_config_to_docker_uv_lock(
)
config_root = config_path.parent.resolve()
source_root = config["source"].get("root", ".")
project_root = (config_root / source_root).resolve()
_validate_git_http_url_userinfo_files(
[project_root / "pyproject.toml", project_root / "uv.lock"]
)
install_cmd = "uv pip install --system"
_, global_reqs_pip_install, pip_config_file_str = _build_python_install_commands(
config, install_cmd
)
plan = _plan_uv_lock_workspace(config_path, config)
_validate_git_http_url_userinfo_files(
package.pyproject_path for package in plan.install_order
)
_update_uv_lock_graph_paths(config_path, config, plan)
for section, key in [
@@ -970,6 +980,22 @@ def python_config_to_docker_uv_lock(
f"{uv_export_project_dir}/uv.lock",
)
)
for package_root in sorted(
plan.all_workspace_roots,
key=lambda root: root.as_posix(),
):
if package_root == plan.project_root:
continue
package_relative_path = pathlib.PurePosixPath(
package_root.relative_to(plan.project_root).as_posix()
)
package_pyproject_path = package_relative_path / "pyproject.toml"
docker_plan.add_raw(
copy_from_project_root(
package_pyproject_path,
f"{uv_export_project_dir}/{package_pyproject_path.as_posix()}",
)
)
docker_plan.add_instruction("WORKDIR", uv_export_project_dir)
docker_plan.add_instruction(
"RUN",
+2 -2
View File
@@ -28,7 +28,7 @@
"type": "null"
}
],
"description": "Optional. Path to a pip config file (e.g., \"/etc/pip.conf\" or \"pip.ini\") for controlling\npackage installation (custom indices, credentials, etc.).\n\nOnly relevant if Python dependencies are installed via pip. If omitted, default pip settings are used.\n"
"description": "Optional. Path to a pip config file (e.g., \"/etc/pip.conf\" or \"pip.ini\") for controlling\npackage installation (custom indices, timeouts, etc.). The file is copied into the\ngenerated image, so it must not contain credentials or other secrets.\n\nOnly relevant if Python dependencies are installed via pip. If omitted, default pip settings are used.\n"
},
"_INTERNAL_docker_tag": {
"anyOf": [
@@ -270,7 +270,7 @@
"type": "null"
}
],
"description": "Optional. Path to a pip config file (e.g., \"/etc/pip.conf\" or \"pip.ini\") for controlling\npackage installation (custom indices, credentials, etc.).\n\nOnly relevant if Python dependencies are installed via pip. If omitted, default pip settings are used.\n"
"description": "Optional. Path to a pip config file (e.g., \"/etc/pip.conf\" or \"pip.ini\") for controlling\npackage installation (custom indices, timeouts, etc.). The file is copied into the\ngenerated image, so it must not contain credentials or other secrets.\n\nOnly relevant if Python dependencies are installed via pip. If omitted, default pip settings are used.\n"
},
"_INTERNAL_docker_tag": {
"anyOf": [
+2 -2
View File
@@ -28,7 +28,7 @@
"type": "null"
}
],
"description": "Optional. Path to a pip config file (e.g., \"/etc/pip.conf\" or \"pip.ini\") for controlling\npackage installation (custom indices, credentials, etc.).\n\nOnly relevant if Python dependencies are installed via pip. If omitted, default pip settings are used.\n"
"description": "Optional. Path to a pip config file (e.g., \"/etc/pip.conf\" or \"pip.ini\") for controlling\npackage installation (custom indices, timeouts, etc.). The file is copied into the\ngenerated image, so it must not contain credentials or other secrets.\n\nOnly relevant if Python dependencies are installed via pip. If omitted, default pip settings are used.\n"
},
"_INTERNAL_docker_tag": {
"anyOf": [
@@ -270,7 +270,7 @@
"type": "null"
}
],
"description": "Optional. Path to a pip config file (e.g., \"/etc/pip.conf\" or \"pip.ini\") for controlling\npackage installation (custom indices, credentials, etc.).\n\nOnly relevant if Python dependencies are installed via pip. If omitted, default pip settings are used.\n"
"description": "Optional. Path to a pip config file (e.g., \"/etc/pip.conf\" or \"pip.ini\") for controlling\npackage installation (custom indices, timeouts, etc.). The file is copied into the\ngenerated image, so it must not contain credentials or other secrets.\n\nOnly relevant if Python dependencies are installed via pip. If omitted, default pip settings are used.\n"
},
"_INTERNAL_docker_tag": {
"anyOf": [
+250
View File
@@ -255,6 +255,243 @@ def test_validate_config():
)
@pytest.mark.parametrize(
"dependency",
[
"git+https://user:secret-token@github.com/org/private.git@main",
"private-package @ git+http://token@github.com/org/private.git",
"git+HTTPS://user%40example.com:secret%2Ftoken@github.com/org/private.git",
"git+https://${GIT_TOKEN}@github.com/org/private.git",
],
)
def test_validate_config_rejects_git_http_url_userinfo(dependency: str):
with pytest.raises(click.UsageError) as exc_info:
validate_config(
{
"python_version": "3.11",
"dependencies": [dependency],
"graphs": {"agent": "./agent.py:graph"},
}
)
message = str(exc_info.value)
assert "must not contain credentials or other URL userinfo" in message
assert "secret-token" not in message
assert "secret%2Ftoken" not in message
def test_validate_config_file_reports_source_for_git_http_url_userinfo(
tmp_path: pathlib.Path,
):
config_path = tmp_path / "langgraph.json"
config_path.write_text(
json.dumps(
{
"python_version": "3.11",
"dependencies": ["git+https://secret-token@github.com/org/private.git"],
"graphs": {"agent": "./agent.py:graph"},
}
)
)
with pytest.raises(click.UsageError) as exc_info:
validate_config_file(config_path)
message = str(exc_info.value)
assert "secret-token" not in message
assert f"Found in: {config_path.resolve()}" in message
@pytest.mark.parametrize(
"manifest", ["package.json", "package-lock.json", "yarn.lock", "pnpm-lock.yaml"]
)
def test_config_to_docker_rejects_git_http_url_userinfo_in_node_files(
tmp_path: pathlib.Path, manifest: str
):
config_path = tmp_path / "langgraph.json"
config_path.write_text("{}\n")
(tmp_path / "agent.js").write_text("export const graph = {};\n")
(tmp_path / "package.json").write_text('{"name":"agent"}\n')
(tmp_path / manifest).write_text(
'"priv": "git+https://user:secret-token@github.com/org/private.git"\n'
)
config = validate_config(
{
"node_version": "20",
"graphs": {"agent": "./agent.js:graph"},
}
)
with pytest.raises(click.UsageError) as exc_info:
config_to_docker(
config_path,
config,
base_image="langchain/langgraphjs-api",
)
message = str(exc_info.value)
assert "must not contain credentials or other URL userinfo" in message
assert "secret-token" not in message
assert f"Found in: {(tmp_path / manifest).resolve()}" in message
def test_config_to_docker_allows_node_git_urls_without_http_userinfo(
tmp_path: pathlib.Path,
):
config_path = tmp_path / "langgraph.json"
config_path.write_text("{}\n")
(tmp_path / "agent.js").write_text("export const graph = {};\n")
(tmp_path / "package.json").write_text(
'{"dependencies":{"public":"git+https://github.com/org/public.git"}}\n'
)
config = validate_config(
{
"node_version": "20",
"graphs": {"agent": "./agent.js:graph"},
}
)
docker, _ = config_to_docker(
config_path,
config,
base_image="langchain/langgraphjs-api",
)
assert f"ADD . /deps/{tmp_path.name}" in docker
def test_config_to_docker_rejects_git_http_url_userinfo_in_node_workspace(
tmp_path: pathlib.Path,
):
config_root = tmp_path / "apps" / "agent"
config_root.mkdir(parents=True)
config_path = config_root / "langgraph.json"
config_path.write_text("{}\n")
(config_root / "agent.js").write_text("export const graph = {};\n")
(config_root / "package.json").write_text(
'{"dependencies":{"priv":"git+https://secret-token@github.com/org/private.git"}}\n'
)
(tmp_path / "package.json").write_text('{"name":"workspace"}\n')
config = validate_config(
{
"node_version": "20",
"graphs": {"agent": "./agent.js:graph"},
}
)
with pytest.raises(click.UsageError) as exc_info:
config_to_docker(
config_path,
config,
base_image="langchain/langgraphjs-api",
build_context=str(tmp_path),
)
message = str(exc_info.value)
assert "secret-token" not in message
assert f"Found in: {(config_root / 'package.json').resolve()}" in message
@pytest.mark.parametrize(
"dependency",
[
"git+https://github.com/org/public.git@main",
"private-package @ git+https://github.com/org/private.git@main",
"git+ssh://git@github.com/org/private.git@main",
],
)
def test_validate_config_allows_git_urls_without_http_userinfo(dependency: str):
config = validate_config(
{
"python_version": "3.11",
"dependencies": [dependency],
"graphs": {"agent": "./agent.py:graph"},
}
)
assert config["dependencies"] == [dependency]
def test_config_to_docker_rejects_git_http_url_userinfo_in_requirements(
tmp_path: pathlib.Path,
):
config_path = tmp_path / "langgraph.json"
config_path.write_text("{}\n")
(tmp_path / "agent.py").write_text("graph = object()\n")
(tmp_path / "requirements.txt").write_text(
"private @ git+https://secret-token@github.com/org/private.git\n"
)
config = validate_config(
{
"python_version": "3.11",
"dependencies": ["."],
"graphs": {"agent": "./agent.py:graph"},
}
)
with pytest.raises(click.UsageError) as exc_info:
config_to_docker(
config_path,
config,
base_image="langchain/langgraph-api:0.2.47",
)
message = str(exc_info.value)
assert "must not contain credentials or other URL userinfo" in message
assert "secret-token" not in message
assert f"Found in: {(tmp_path / 'requirements.txt').resolve()}" in message
@pytest.mark.parametrize("manifest", ["pyproject.toml", "uv.lock"])
def test_config_to_docker_rejects_git_http_url_userinfo_in_uv_files(
tmp_path: pathlib.Path, manifest: str
):
config_path = tmp_path / "langgraph.json"
config_path.write_text("{}\n")
(tmp_path / "src").mkdir()
(tmp_path / "src" / "agent.py").write_text("graph = object()\n")
pyproject = textwrap.dedent(
"""
[project]
name = "agent"
version = "0.1.0"
dependencies = ["private"]
[tool.uv.sources]
private = { git = "https://github.com/org/private.git" }
"""
).strip()
uv_lock = "# uv lock file\n"
if manifest == "pyproject.toml":
pyproject = pyproject.replace(
"https://github.com", "https://secret-token@github.com"
)
else:
uv_lock += (
'source = { git = "https://secret-token@github.com/org/private.git" }\n'
)
(tmp_path / "pyproject.toml").write_text(pyproject + "\n")
(tmp_path / "uv.lock").write_text(uv_lock)
config = validate_config(
{
"python_version": "3.11",
"graphs": {"agent": "./src/agent.py:graph"},
"source": {"kind": "uv"},
}
)
with pytest.raises(click.UsageError) as exc_info:
config_to_docker(
config_path,
config,
base_image="langchain/langgraph-api:0.2.47",
)
message = str(exc_info.value)
assert "must not contain credentials or other URL userinfo" in message
assert "secret-token" not in message
def test_validate_config_image_distro():
"""Test validation of image_distro field."""
# Valid image_distro values should work
@@ -1403,6 +1640,19 @@ def test_config_to_docker_uv_lock():
"COPY --from=uv-workspace-root uv.lock /tmp/uv_export/project/uv.lock"
in docker
)
workspace_pyprojects = [
"apps/agent/pyproject.toml",
"libs/extra/pyproject.toml",
"libs/shared/pyproject.toml",
]
export_instruction = "RUN uv export --package agent"
for pyproject_path in workspace_pyprojects:
copy_instruction = (
"COPY --from=uv-workspace-root "
f"{pyproject_path} /tmp/uv_export/project/{pyproject_path}"
)
assert copy_instruction in docker
assert docker.index(copy_instruction) < docker.index(export_instruction)
assert additional_contexts == {"uv-workspace-root": str(project_root.resolve())}
assert (
+6 -6
View File
@@ -266,20 +266,20 @@ wheels = [
[[package]]
name = "langgraph-checkpoint"
version = "4.0.1"
version = "4.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "langchain-core" },
{ name = "ormsgpack" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/44/a8df45d1e8b4637e29789fa8bae1db022c953cc7ac80093cfc52e923547e/langgraph_checkpoint-4.0.1.tar.gz", hash = "sha256:b433123735df11ade28829e40ce25b9be614930cd50245ff2af60629234befd9", size = 158135, upload-time = "2026-02-27T21:06:16.092Z" }
sdist = { url = "https://files.pythonhosted.org/packages/dc/e1/089c4c9e0a2fec7f883f82ae8e6a727138d50074cfeb6644bc2d13b1019b/langgraph_checkpoint-4.2.0.tar.gz", hash = "sha256:51a593b6bee684b0818e5d6e58e28ab340c6db7794575056ce7bd1b746a84ed7", size = 180239, upload-time = "2026-08-07T20:05:03.756Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/65/4c/09a4a0c42f5d2fc38d6c4d67884788eff7fd2cfdf367fdf7033de908b4c0/langgraph_checkpoint-4.0.1-py3-none-any.whl", hash = "sha256:e3adcd7a0e0166f3b48b8cf508ce0ea366e7420b5a73aa81289888727769b034", size = 50453, upload-time = "2026-02-27T21:06:14.293Z" },
{ url = "https://files.pythonhosted.org/packages/05/71/3b475f09bd57d3a5649792c66353312b4432afd843f301739dfcebd157f0/langgraph_checkpoint-4.2.0-py3-none-any.whl", hash = "sha256:0547fd228935a0b758865de3a3d6d7a2537c308895d0f9ab092ce9151b5da942", size = 56833, upload-time = "2026-08-07T20:05:02.655Z" },
]
[[package]]
name = "langgraph-checkpoint-postgres"
version = "3.0.5"
version = "3.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "langgraph-checkpoint" },
@@ -287,9 +287,9 @@ dependencies = [
{ name = "psycopg" },
{ name = "psycopg-pool" },
]
sdist = { url = "https://files.pythonhosted.org/packages/95/7a/8f439966643d32111248a225e6cb33a182d07c90de780c4dbfc1e0377832/langgraph_checkpoint_postgres-3.0.5.tar.gz", hash = "sha256:a8fd7278a63f4f849b5cbc7884a15ca8f41e7d5f7467d0a66b31e8c24492f7eb", size = 127856, upload-time = "2026-03-18T21:25:29.785Z" }
sdist = { url = "https://files.pythonhosted.org/packages/06/92/1e8959f8cd1b56e672fde3227f6fd642be85af6c5fd662d73921074aa39d/langgraph_checkpoint_postgres-3.1.1.tar.gz", hash = "sha256:d320e147ddad8c374cd546df0b52b532dd54d0541dd9fd23fc738cbd5de76f41", size = 150413, upload-time = "2026-07-30T19:15:39.014Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e8/87/b0f98b33a67204bca9d5619bcd9574222f6b025cf3c125eedcec9a50ecbc/langgraph_checkpoint_postgres-3.0.5-py3-none-any.whl", hash = "sha256:86d7040a88fd70087eaafb72251d796696a0a2d856168f5c11ef620771411552", size = 42907, upload-time = "2026-03-18T21:25:28.75Z" },
{ url = "https://files.pythonhosted.org/packages/03/32/ba457698a48a0e18d786caa770033067049fbe36d6846f8e50f13b594b51/langgraph_checkpoint_postgres-3.1.1-py3-none-any.whl", hash = "sha256:6e353aecd8150de144fef8e51a49076f58b7d6830d4cf51392b7ad4d79832ba7", size = 50778, upload-time = "2026-07-30T19:15:37.405Z" },
]
[[package]]