Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
20c1c08
ci: add security-aware Dependabot updates for Python and GitHub Actions
HAYDEN-OAI Aug 17, 2026
4688d3b
fix: track Python lockfiles and pinned Actions securely
HAYDEN-OAI Aug 17, 2026
42e7f73
fix: preserve release marker in uv dependency lock
HAYDEN-OAI Aug 17, 2026
9eacb6d
fix: use Python 3.11 for dependency lock policy checks
HAYDEN-OAI Aug 17, 2026
05d03fc
Merge main and reconcile security dependency locks
HAYDEN-OAI Aug 18, 2026
253692a
Merge main and preserve uv-native dependency security
HAYDEN-OAI Aug 19, 2026
86b9b62
fix: reject untrusted uv lockfile dependency sources
HAYDEN-OAI Aug 19, 2026
6af5be9
fix: gate dependency installs on lock provenance
HAYDEN-OAI Aug 19, 2026
69acefe
fix: validate dependency artifacts and security update floors
HAYDEN-OAI Aug 19, 2026
70d1ffa
Merge main into dependency security policy updates
HAYDEN-OAI Aug 19, 2026
3e188b8
fix:enforce-strict-dependabot-security-dependency-floors
HAYDEN-OAI Aug 19, 2026
0b3f84b
fix: validate dependency floors against patched lock releases
HAYDEN-OAI Aug 19, 2026
d5e8f30
fix: reject weakened security floors with unchanged locks
HAYDEN-OAI Aug 19, 2026
05d1c29
fix: preserve contextual security floors and reject lock downgrades
HAYDEN-OAI Aug 19, 2026
2a4d89e
fix: validate root build provenance and preserve security constraints
HAYDEN-OAI Aug 19, 2026
dd2e769
fix: block dependency source builds and preserve marker-specific secu…
HAYDEN-OAI Aug 19, 2026
3860753
fix(ci): permit reviewed editable roots without building dependencies
HAYDEN-OAI Aug 19, 2026
27b0b83
fix(ci): reject direct dependency removals in security updates
HAYDEN-OAI Aug 19, 2026
2b16120
fix(ci): anchor fork build backends to the trusted base
HAYDEN-OAI Aug 19, 2026
4d25ed8
fix(ci): require protected floors to reach patched releases
HAYDEN-OAI Aug 19, 2026
f1f729e
fix(ci): preserve marker-specific security floors and trusted source …
HAYDEN-OAI Aug 19, 2026
eb134b8
fix(ci): defer source builds and support dependency marker membership
HAYDEN-OAI Aug 19, 2026
9071f2b
fix: preserve supported dependency security branches
HAYDEN-OAI Aug 19, 2026
05c414f
fix(security): require safe published dependency branches
HAYDEN-OAI Aug 19, 2026
f012570
fix(security): preserve bounds when dependency locks are unchanged
HAYDEN-OAI Aug 19, 2026
1ef8c69
fix(security): preserve all reviewed dependency bounds
HAYDEN-OAI Aug 20, 2026
a0049ec
fix(security): anchor dependency checks to immutable trusted bases
HAYDEN-OAI Aug 20, 2026
7a3b31a
fix(security): preserve reviewed exact dependency pins
HAYDEN-OAI Aug 20, 2026
95769f4
fix(security): require boundaries for transitive dependency patches
HAYDEN-OAI Aug 20, 2026
d650c88
fix(security): preserve reviewed dependency boundaries and extras
HAYDEN-OAI Aug 20, 2026
1e1b908
fix(security): guard fork hooks and published dependency exposure
HAYDEN-OAI Aug 20, 2026
24c14ae
fix: validate dependency security across marker domains
HAYDEN-OAI Aug 24, 2026
ace8805
test: annotate dependency edge security fixture
HAYDEN-OAI Aug 24, 2026
e5a9d7f
fix: validate marker provenance and all supported security branches
HAYDEN-OAI Aug 24, 2026
2ad0073
fix: reconcile resolution domains and scoped extra reviews
HAYDEN-OAI Aug 24, 2026
a4091b1
fix: remove unused dependency reachability state
HAYDEN-OAI Aug 25, 2026
8ca4367
Preserve semantic marker and wildcard security bounds
HAYDEN-OAI Aug 25, 2026
03dc92e
Validate the complete relinked Agents dependency lock
HAYDEN-OAI Aug 25, 2026
439a5dd
Preserve exact Python marker security semantics
HAYDEN-OAI Aug 25, 2026
8ff2861
Constrain Agents relinking to reviewed dependency versions
HAYDEN-OAI Aug 25, 2026
51f3e20
fix(ci): preserve reviewed no-build Agents dependencies
HAYDEN-OAI Aug 25, 2026
1c6dc30
fix(ci): type reviewed Agents dependency artifacts
HAYDEN-OAI Aug 25, 2026
b34e719
fix: address dependency security review feedback
HAYDEN-OAI Aug 25, 2026
232794d
fix: align dependency bounds and marker evaluation
HAYDEN-OAI Aug 25, 2026
21e42f0
fix: preserve shared extra and platform marker semantics
HAYDEN-OAI Aug 25, 2026
e883403
fix: preserve post-release floors and marker orientation
HAYDEN-OAI Aug 25, 2026
8be8386
Merge branch 'main' into codex/openai-python-dependabot-security-2026…
HAYDEN-OAI Aug 25, 2026
923053f
fix: preserve dependency audiences and PEP 440 marker versions
HAYDEN-OAI Aug 25, 2026
e2c40e5
fix: preserve complete PEP 440 security marker domains
HAYDEN-OAI Aug 25, 2026
2e94eda
fix: harden security dependency markers and source builds
HAYDEN-OAI Aug 25, 2026
210bf74
fix: preserve security marker release and membership domains
HAYDEN-OAI Aug 25, 2026
eedfd70
fix: preserve complete dependency security marker boundaries
HAYDEN-OAI Aug 25, 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
27 changes: 27 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,41 @@ updates:
default-days: 8
- package-ecosystem: uv
directory: /
versioning-strategy: increase-if-necessary
Comment thread
HAYDEN-OAI marked this conversation as resolved.
schedule:
interval: weekly
day: monday
time: "09:00"
timezone: Etc/UTC
# Version updates wait; Dependabot security updates are exempt.
cooldown:
default-days: 8
open-pull-requests-limit: 3
groups:
python-maintenance:
applies-to: version-updates
patterns:
- "*"
update-types:
- minor
- patch
python-security:
applies-to: security-updates
patterns:
- "*"

- package-ecosystem: github-actions
directory: /
Comment thread
HAYDEN-OAI marked this conversation as resolved.
schedule:
interval: weekly
day: monday
time: "09:30"
timezone: Etc/UTC
cooldown:
default-days: 8
open-pull-requests-limit: 5
groups:
# CodeQL init/analyze share versioned state and must change together.
codeql:
patterns:
- "github/codeql-action"
58 changes: 57 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,58 @@ on:
permissions:
contents: read

env:
UV_NO_BUILD: '1'
Comment thread
HAYDEN-OAI marked this conversation as resolved.
UV_NO_BINARY_PACKAGE: 'openai'
UNTRUSTED_BUILD_FORK: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id && '1' || '0' }}
TRUSTED_BUILD_BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || '' }}

jobs:
dependency-locks:
Comment thread
HAYDEN-OAI marked this conversation as resolved.
timeout-minutes: 10
name: dependency lock freshness
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false

- name: Verify dependency source provenance before installing tools
run: |
python -I -c 'import ast, os, pathlib, re, subprocess, tomllib; config = tomllib.loads(pathlib.Path("pyproject.toml").read_text()); fork = os.environ.get("UNTRUSTED_BUILD_FORK", "0") == "1"; base = os.environ.get("TRUSTED_BUILD_BASE_SHA", ""); assert not fork or re.fullmatch(r"[0-9a-f]{40}", base), "Untrusted fork build base commit"; assert not fork or subprocess.check_output(["git", "remote", "get-url", "origin"], text=True).strip() in {"https://github.com/openai/openai-python", "https://github.com/openai/openai-python.git"}, "Untrusted fork build base origin"; subprocess.run(["git", "fetch", "--no-tags", "--depth=1", "origin", base], check=True) if fork else None; trusted = tomllib.loads(subprocess.check_output(["git", "show", base + ":pyproject.toml"], text=True)) if fork else config; hatch = lambda document: document.get("tool", {}).get("hatch", {}); executable = lambda document: (hatch(document).get("build", {}).get("hooks", {}), tuple(sorted((name, target.get("hooks", {})) for name, target in hatch(document).get("build", {}).get("targets", {}).items() if target.get("hooks", {}))), hatch(document).get("metadata", {}).get("hooks", {})); assert not fork or executable(config) == executable(trusted), "Untrusted fork Hatch executable hook configuration"; sections = executable(config); paths = [definition.get("path", "hatch_build.py") for mapping in [sections[0], *[item[1] for item in sections[1]], sections[2]] for plugin, definition in mapping.items() if plugin == "custom" or "path" in definition] if fork else []; assert not fork or len(paths) <= 32 and all(isinstance(location, str) and bool(location) and len(location) <= 512 and not (candidate := pathlib.Path(location)).is_absolute() and bool(candidate.parts) and ".." not in candidate.parts and candidate.is_file() and all(not pathlib.Path(*candidate.parts[:index]).is_symlink() for index in range(1, len(candidate.parts) + 1)) and candidate.read_bytes() == subprocess.check_output(["git", "show", base + ":" + candidate.as_posix()]) for location in paths), "Untrusted fork Hatch executable hook source"; project = config["project"]; lock = tomllib.loads(pathlib.Path("uv.lock").read_text()); trusted_lock = tomllib.loads(subprocess.check_output(["git", "show", base + ":uv.lock"], text=True)) if fork else lock; roots = [package for package in lock["package"] if package.get("name") == project["name"] and package.get("version") == project["version"] and package.get("source") == {"editable": "."}]; build = config.get("build-system", {}); uv = config.get("tool", {}).get("uv", {}); requires = build.get("requires", []); constraints = uv.get("build-constraint-dependencies", []); reviewed = config.get("dependency-groups", {}).get("build", []); canonical = lambda value: re.sub(r"[-_.]+", "-", value).lower(); package_identity = lambda package: (canonical(package["name"]), package["version"], package.get("source", {}).get("registry", ""), tuple(sorted((artifact["url"], artifact["hash"]) for artifact in (([package["sdist"]] if "sdist" in package else []) + package.get("wheels", []))))); normalize = lambda value: re.sub(r"\s+", "", value); identity = lambda value: (canonical((item := re.fullmatch(r"([A-Za-z0-9][A-Za-z0-9_.-]*)\s*==\s*([0-9]+(?:\.[0-9]+)*(?:\.post[0-9]+)?)(?:\s*;\s*(.+))?", value)).group(1)), item.group(2), ast.dump(ast.parse(item.group(3), mode="eval")) if item.group(3) is not None else ""); assert len(roots) == 1 and canonical(project["name"]) == "openai" and sum(canonical(package.get("name", "")) == "openai" for package in lock["package"]) == 1 and all(package is roots[0] or package.get("source") == {"registry": "https://pypi.org/simple"} and isinstance(package.get("wheels"), list) and bool(package.get("wheels")) and (artifacts := ([package["sdist"]] if "sdist" in package else []) + package.get("wheels", [])) and all(isinstance(artifact, dict) and re.fullmatch(r"https://files\.pythonhosted\.org/packages/[^\s?#]+", artifact.get("url", "")) and re.fullmatch(r"sha256:[0-9a-f]{64}", artifact.get("hash", "")) for artifact in artifacts) for package in lock["package"]) and (not fork or sorted(package_identity(package) for package in lock["package"] if package is not roots[0]) == sorted(package_identity(package) for package in trusted_lock["package"] if package.get("source") != {"editable": "."})) and build.get("build-backend") == "hatchling.build" and "backend-path" not in build and isinstance(requires, list) and bool(requires) and isinstance(constraints, list) and bool(constraints) and isinstance(reviewed, list) and bool(reviewed) and not any(uv.get(key) for key in ("sources", "index", "indexes", "index-url", "extra-index-url", "default-index", "find-links", "index_url", "extra_index_url", "default_index", "find_links", "no-binary", "no-binary-package", "no_binary", "no_binary_package")) and not any(pathlib.Path(path).exists() for path in ("uv.toml", ".uv.toml")) and all(isinstance(requirement, str) and (match := re.fullmatch(r"([A-Za-z0-9][A-Za-z0-9_.-]*)\s*==\s*([0-9]+(?:\.[0-9]+)*(?:\.post[0-9]+)?)(?:\s*;\s*(.+))?", requirement)) is not None and (match.group(3) is None or isinstance(marker := ast.parse(match.group(3), mode="eval").body, ast.Compare) and isinstance(marker.left, ast.Name) and len(marker.ops) == 1 and type(marker.ops[0]) in {ast.Eq, ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE, ast.In, ast.NotIn} and len(marker.comparators) == 1 and isinstance(marker.comparators[0], ast.Constant) and isinstance(marker.comparators[0].value, str)) and any(canonical(package.get("name", "")) == canonical(match.group(1)) and package.get("version") == match.group(2) and package.get("source") == {"registry": "https://pypi.org/simple"} for package in lock["package"]) for requirement in requires + constraints + reviewed) and {normalize(requirement) for requirement in constraints} == {normalize(requirement) for requirement in reviewed} and {normalize(requirement) for requirement in requires}.issubset({normalize(requirement) for requirement in constraints}) and any(canonical(requirement.split("==", 1)[0].strip()) == "hatchling" for requirement in requires) and build.get("build-backend") == trusted.get("build-system", {}).get("build-backend") and {identity(requirement) for requirement in requires} == {identity(requirement) for requirement in trusted.get("build-system", {}).get("requires", [])} and {identity(requirement) for requirement in constraints} == {identity(requirement) for requirement in trusted.get("tool", {}).get("uv", {}).get("build-constraint-dependencies", [])} and {identity(requirement) for requirement in reviewed} == {identity(requirement) for requirement in trusted.get("dependency-groups", {}).get("build", [])}, "Use only the public PyPI registry, reviewed artifacts, the exact editable root project, and locked reviewed build requirements in uv.lock"'

- name: Require published minimums for direct security updates
if: >-
github.event_name == 'pull_request'
&& github.event.pull_request.user.login == 'dependabot[bot]'
&& contains(github.event.pull_request.head.ref, 'python-security')
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
set -euo pipefail
[[ "$BASE_SHA" =~ ^[0-9a-f]{40}$ ]]
origin="$(git remote get-url origin)"
[[ "$origin" == "https://github.com/openai/openai-python" || "$origin" == "https://github.com/openai/openai-python.git" ]]
git fetch --no-tags --depth=1 origin "$BASE_SHA"
git show "$BASE_SHA:scripts/check-dependency-security.py" | python -I -

Comment thread
HAYDEN-OAI marked this conversation as resolved.
- name: Set up uv
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
version: '0.12.5'
enable-cache: true

- name: Verify dependency groups and committed lock
run: |
uv run --no-project --python '>=3.11' python -c 'import pathlib, tomllib; project = tomllib.loads(pathlib.Path("pyproject.toml").read_text()); assert project["dependency-groups"]["dev"] and "dev" in project["tool"]["uv"]["default-groups"], "Keep the uv development dependency group enabled"'
uv run --no-project --python '>=3.11' python -c 'import pathlib, tomllib; project = tomllib.loads(pathlib.Path("pyproject.toml").read_text()); text = pathlib.Path("uv.lock").read_text(); lock = tomllib.loads(text); version = project["project"]["version"]; assert next(package["version"] for package in lock["package"] if package["name"] == "openai") == version and "version = " + chr(34) + version + chr(34) + " # x-release-please-version" in text, "Preserve the uv.lock release-please marker and project version"'
uv lock --check

lint:
timeout-minutes: 10
name: lint
runs-on: ubuntu-latest
needs: dependency-locks
if: github.event_name == 'push' || github.event_name == 'merge_group' || github.event.pull_request.head.repo.fork
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
Expand All @@ -44,6 +91,7 @@ jobs:
run: ./scripts/lint

build:
needs: dependency-locks
if: github.event_name == 'push' || github.event_name == 'merge_group' || github.event.pull_request.head.repo.fork
timeout-minutes: 10
name: build
Expand All @@ -65,7 +113,9 @@ jobs:
run: uv sync --locked --all-extras

- name: Run build
run: ./scripts/build
run: |
external_packages="$(python -c 'import importlib, re, sys; parser = importlib.import_module("tomllib" if sys.version_info >= (3, 11) else "tomli"); packages = parser.loads(open("uv.lock").read())["package"]; roots = [package for package in packages if package.get("source") == {"editable": "."}]; external = [package for package in packages if package not in roots]; assert len(roots) == 1 and roots[0].get("name") == "openai" and external and all(package.get("source") == {"registry": "https://pypi.org/simple"} and isinstance(package.get("name"), str) and re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_.-]*", package["name"]) for package in external), "Refuse malformed external build package restrictions"; denied = sorted({re.sub(r"[-_.]+", "-", package["name"]).lower() for package in external}); assert denied and "openai" not in denied, "Refuse incomplete external build package restrictions"; print(" ".join(denied))')"
env -u UV_NO_BUILD UV_NO_BUILD_PACKAGE="$external_packages" ./scripts/build

- name: Validate Python version metadata
run: uv run --locked --all-extras python scripts/utils/validate-python-version-wheel.py
Expand Down Expand Up @@ -96,6 +146,7 @@ jobs:
timeout-minutes: 15
name: test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
needs: dependency-locks
if: github.event_name == 'push' || github.event_name == 'merge_group' || github.event.pull_request.head.repo.fork
strategy:
fail-fast: false
Expand Down Expand Up @@ -132,6 +183,7 @@ jobs:
timeout-minutes: 20
name: test (HTTPX2)
runs-on: ubuntu-latest
needs: dependency-locks
if: github.event_name == 'push' || github.event_name == 'merge_group' || github.event.pull_request.head.repo.fork
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
Expand Down Expand Up @@ -167,6 +219,7 @@ jobs:
name: examples
environment: ci
runs-on: ubuntu-latest
needs: dependency-locks
if: github.repository == 'openai/openai-python' && github.event_name == 'push' && github.ref == 'refs/heads/main'

steps:
Expand Down Expand Up @@ -209,8 +262,11 @@ jobs:
timeout-minutes: 20
name: compatibility (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
needs: dependency-locks
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
continue-on-error: ${{ matrix.experimental }}
env:
UV_NO_BUILD: ${{ matrix.experimental && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && '0' || '1' }}
strategy:
fail-fast: false
matrix:
Expand Down
Loading
Loading