Initial commit: AirCoding V1.0.0 Alpha architecture baseline
Complete architecture document set with multi-model review remediation: - Frozen interface contracts, runtime semantics, DB schemas - Event/tool/error/provider registries - Scheduler and main agent state machines - C4 module/code views, solution architecture, baseline V1 - Multi-model review reports and joint assessment - Phase-gate remediation complete (P0/P1/P2/UX resolved) - Implementation plan with T-000A through T-045 - Reference folders kept as placeholders only
This commit is contained in:
27
reference/openai-codex/.devcontainer/Dockerfile
Normal file
27
reference/openai-codex/.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
# enable 'universe' because musl-tools & clang live there
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
software-properties-common && \
|
||||
add-apt-repository --yes universe
|
||||
|
||||
# now install build deps
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential curl git ca-certificates \
|
||||
pkg-config libcap-dev clang musl-tools libssl-dev just && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Ubuntu 24.04 ships with user 'ubuntu' already created with UID 1000.
|
||||
USER ubuntu
|
||||
|
||||
# install Rust + musl target as dev user
|
||||
RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal && \
|
||||
~/.cargo/bin/rustup target add aarch64-unknown-linux-musl && \
|
||||
~/.cargo/bin/rustup component add clippy rustfmt
|
||||
|
||||
ENV PATH="/home/ubuntu/.cargo/bin:${PATH}"
|
||||
|
||||
WORKDIR /workspace
|
||||
82
reference/openai-codex/.devcontainer/Dockerfile.secure
Normal file
82
reference/openai-codex/.devcontainer/Dockerfile.secure
Normal file
@@ -0,0 +1,82 @@
|
||||
FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04
|
||||
|
||||
ARG TZ
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG NODE_MAJOR=22
|
||||
ARG RUST_TOOLCHAIN=1.92.0
|
||||
# Keep this in sync with .devcontainer/codex-install/package.json and pnpm-lock.yaml.
|
||||
ARG CODEX_NPM_VERSION=0.121.0
|
||||
|
||||
ENV TZ="$TZ"
|
||||
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
# Devcontainers run as a non-root user, so enable bubblewrap's setuid mode.
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
curl \
|
||||
git \
|
||||
ca-certificates \
|
||||
pkg-config \
|
||||
clang \
|
||||
musl-tools \
|
||||
libssl-dev \
|
||||
libsqlite3-dev \
|
||||
just \
|
||||
python3 \
|
||||
python3-pip \
|
||||
jq \
|
||||
less \
|
||||
man-db \
|
||||
unzip \
|
||||
ripgrep \
|
||||
fzf \
|
||||
fd-find \
|
||||
zsh \
|
||||
dnsutils \
|
||||
iproute2 \
|
||||
ipset \
|
||||
iptables \
|
||||
aggregate \
|
||||
bubblewrap \
|
||||
&& chmod u+s /usr/bin/bwrap \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY .devcontainer/codex-install/package.json \
|
||||
.devcontainer/codex-install/pnpm-lock.yaml \
|
||||
.devcontainer/codex-install/pnpm-workspace.yaml \
|
||||
/opt/codex-install/
|
||||
|
||||
RUN curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash - \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends nodejs \
|
||||
&& test "$(node -p "require('/opt/codex-install/package.json').dependencies['@openai/codex']")" = "${CODEX_NPM_VERSION}" \
|
||||
&& cd /opt/codex-install \
|
||||
&& corepack pnpm install --prod --frozen-lockfile \
|
||||
&& ln -s /opt/codex-install/node_modules/.bin/codex /usr/local/bin/codex \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY .devcontainer/init-firewall.sh /usr/local/bin/init-firewall.sh
|
||||
COPY .devcontainer/post_install.py /opt/post_install.py
|
||||
COPY .devcontainer/post-start.sh /opt/post_start.sh
|
||||
|
||||
RUN chmod 500 /usr/local/bin/init-firewall.sh \
|
||||
&& chmod 755 /opt/post_start.sh \
|
||||
&& chmod 644 /opt/post_install.py \
|
||||
&& chown vscode:vscode /opt/post_install.py
|
||||
|
||||
RUN install -d -m 0775 -o vscode -g vscode /commandhistory /workspace \
|
||||
&& touch /commandhistory/.bash_history /commandhistory/.zsh_history \
|
||||
&& chown vscode:vscode /commandhistory/.bash_history /commandhistory/.zsh_history
|
||||
|
||||
USER vscode
|
||||
ENV PATH="/home/vscode/.cargo/bin:${PATH}"
|
||||
WORKDIR /workspace
|
||||
|
||||
RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain "${RUST_TOOLCHAIN}" \
|
||||
&& rustup component add clippy rustfmt rust-src \
|
||||
&& rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl
|
||||
49
reference/openai-codex/.devcontainer/README.md
Normal file
49
reference/openai-codex/.devcontainer/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Containerized Development
|
||||
|
||||
We provide two container paths:
|
||||
|
||||
- `devcontainer.json` keeps the existing Codex contributor setup for working on this repository.
|
||||
- `devcontainer.secure.json` adds a customer-oriented profile with stricter outbound network controls.
|
||||
|
||||
## Codex contributor profile
|
||||
|
||||
Use `devcontainer.json` when you are developing Codex itself. This is the same lightweight arm64 container that already exists in the repo.
|
||||
|
||||
## Secure customer profile
|
||||
|
||||
Use `devcontainer.secure.json` when you want a stricter runtime profile for running Codex inside a project container:
|
||||
|
||||
- installs the Codex CLI plus common build tools
|
||||
- installs bubblewrap in setuid mode for Codex's Linux sandbox
|
||||
- disables Docker's outer seccomp and AppArmor profiles so bubblewrap can construct Codex's inner sandbox
|
||||
- enables firewall startup with an allowlist-driven outbound policy
|
||||
- blocks IPv6 by default so the allowlist cannot be bypassed over AAAA routes
|
||||
- requires `NET_ADMIN` and `NET_RAW` so the firewall can be installed at startup
|
||||
|
||||
This profile keeps the stricter networking isolated to the customer path instead of changing the default Codex contributor container.
|
||||
|
||||
Start it from the CLI with:
|
||||
|
||||
```bash
|
||||
devcontainer up --workspace-folder . --config .devcontainer/devcontainer.secure.json
|
||||
```
|
||||
|
||||
In VS Code, choose **Dev Containers: Open Folder in Container...** and select `.devcontainer/devcontainer.secure.json`.
|
||||
|
||||
## Docker
|
||||
|
||||
To build the contributor image locally for x64 and then run it with the repo mounted under `/workspace`:
|
||||
|
||||
```shell
|
||||
CODEX_DOCKER_IMAGE_NAME=codex-linux-dev
|
||||
docker build --platform=linux/amd64 -t "$CODEX_DOCKER_IMAGE_NAME" ./.devcontainer
|
||||
docker run --platform=linux/amd64 --rm -it -e CARGO_TARGET_DIR=/workspace/codex-rs/target-amd64 -v "$PWD":/workspace -w /workspace/codex-rs "$CODEX_DOCKER_IMAGE_NAME"
|
||||
```
|
||||
|
||||
Note that `/workspace/target` will contain the binaries built for your host platform, so we include `-e CARGO_TARGET_DIR=/workspace/codex-rs/target-amd64` in the `docker run` command so that the binaries built inside your container are written to a separate directory.
|
||||
|
||||
For arm64, specify `--platform=linux/arm64` instead for both `docker build` and `docker run`.
|
||||
|
||||
Currently, the contributor `Dockerfile` works for both x64 and arm64 Linux, though you need to run `rustup target add x86_64-unknown-linux-musl` yourself to install the musl toolchain for x64.
|
||||
|
||||
The secure profile's capability, seccomp, and AppArmor options are required when you want Codex's bubblewrap sandbox to run inside Docker as the non-root devcontainer user. Without them, Docker's default runtime profile can block bubblewrap's namespace setup before Codex's own seccomp filter is installed. This keeps the Docker relaxation explicit in the profile that is meant to run Codex inside a project container, while the default contributor profile stays lightweight.
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "codex-devcontainer-install",
|
||||
"private": true,
|
||||
"description": "Locked Codex CLI install boundary for the secure devcontainer.",
|
||||
"dependencies": {
|
||||
"@openai/codex": "0.121.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22",
|
||||
"pnpm": ">=10.33.0"
|
||||
},
|
||||
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
|
||||
}
|
||||
85
reference/openai-codex/.devcontainer/codex-install/pnpm-lock.yaml
generated
Normal file
85
reference/openai-codex/.devcontainer/codex-install/pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,85 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@openai/codex':
|
||||
specifier: 0.121.0
|
||||
version: 0.121.0
|
||||
|
||||
packages:
|
||||
|
||||
'@openai/codex@0.121.0':
|
||||
resolution: {integrity: sha512-kCJ2NeATd4QBQRmqV04ymdN1ZU3MSwnJQDm/KzjpuzGvCuUVEn7no/T2mRyxQ2x77AACqriNOyPPoM/yufyvNg==}
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
|
||||
'@openai/codex@0.121.0-darwin-arm64':
|
||||
resolution: {integrity: sha512-ZyBqIB6Fb4I0hGb/h65Vu7ePYjHSmGiqqfm+/1djEuxDPkqjfi4wkxYxNYNY+6najyNGN4UijOSTTf19eDCrqw==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@openai/codex@0.121.0-darwin-x64':
|
||||
resolution: {integrity: sha512-1/OAtdkAZ5yPI3xqaEFlHuPziS1yCqL2gOZdswE7HTmmwpIxi6Z3FCo60JWDPluIp89z4tftdjq73/OCN0YVcw==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@openai/codex@0.121.0-linux-arm64':
|
||||
resolution: {integrity: sha512-2UgMmdo237o7SCMsfb529cOSEM2HFUgN6OBkv5SBLwfNY1NO2Ex6JnUjlppEXlX6/4cXfZ5qjDghVz5j/+B9zw==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@openai/codex@0.121.0-linux-x64':
|
||||
resolution: {integrity: sha512-vlpNJXIqss800J+32Vy7TUZzv31n61b45OLxmsVQGFkTNLJcjFrj9jDUC7I62eC4F16gLioilefNfv4CdJQOEw==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@openai/codex@0.121.0-win32-arm64':
|
||||
resolution: {integrity: sha512-m88q4f3XI5npn1t6OG0nWGHWWAjO5FgjRwxh4hdujbLO6t9CiCNfhfPZIOSsoATbrCNwLC+6S77m3cjbNToPNg==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@openai/codex@0.121.0-win32-x64':
|
||||
resolution: {integrity: sha512-Fp0ecVOyM+VcBi/y4HVvRzhifO9YqRiHzhV3rhtAppC7flh22WPguLC4kmvXYAR0p3RPzbo35M2CedWnkOT+cw==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
snapshots:
|
||||
|
||||
'@openai/codex@0.121.0':
|
||||
optionalDependencies:
|
||||
'@openai/codex-darwin-arm64': '@openai/codex@0.121.0-darwin-arm64'
|
||||
'@openai/codex-darwin-x64': '@openai/codex@0.121.0-darwin-x64'
|
||||
'@openai/codex-linux-arm64': '@openai/codex@0.121.0-linux-arm64'
|
||||
'@openai/codex-linux-x64': '@openai/codex@0.121.0-linux-x64'
|
||||
'@openai/codex-win32-arm64': '@openai/codex@0.121.0-win32-arm64'
|
||||
'@openai/codex-win32-x64': '@openai/codex@0.121.0-win32-x64'
|
||||
|
||||
'@openai/codex@0.121.0-darwin-arm64':
|
||||
optional: true
|
||||
|
||||
'@openai/codex@0.121.0-darwin-x64':
|
||||
optional: true
|
||||
|
||||
'@openai/codex@0.121.0-linux-arm64':
|
||||
optional: true
|
||||
|
||||
'@openai/codex@0.121.0-linux-x64':
|
||||
optional: true
|
||||
|
||||
'@openai/codex@0.121.0-win32-arm64':
|
||||
optional: true
|
||||
|
||||
'@openai/codex@0.121.0-win32-x64':
|
||||
optional: true
|
||||
@@ -0,0 +1,12 @@
|
||||
packages:
|
||||
- "."
|
||||
|
||||
minimumReleaseAge: 10080
|
||||
minimumReleaseAgeExclude: []
|
||||
|
||||
blockExoticSubdeps: true
|
||||
strictDepBuilds: true
|
||||
trustPolicy: no-downgrade
|
||||
trustPolicyIgnoreAfter: 10080
|
||||
trustPolicyExclude: []
|
||||
allowBuilds: {}
|
||||
27
reference/openai-codex/.devcontainer/devcontainer.json
Normal file
27
reference/openai-codex/.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "Codex",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile",
|
||||
"context": "..",
|
||||
"platform": "linux/arm64"
|
||||
},
|
||||
|
||||
/* Force VS Code to run the container as arm64 in
|
||||
case your host is x86 (or vice-versa). */
|
||||
"runArgs": ["--platform=linux/arm64"],
|
||||
|
||||
"containerEnv": {
|
||||
"RUST_BACKTRACE": "1",
|
||||
"CARGO_TARGET_DIR": "${containerWorkspaceFolder}/codex-rs/target-arm64"
|
||||
},
|
||||
|
||||
"remoteUser": "ubuntu",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"terminal.integrated.defaultProfile.linux": "bash"
|
||||
},
|
||||
"extensions": ["rust-lang.rust-analyzer", "tamasfe.even-better-toml"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainer.schema.json",
|
||||
"name": "Codex (Secure)",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile.secure",
|
||||
"context": "..",
|
||||
"args": {
|
||||
"TZ": "${localEnv:TZ:UTC}",
|
||||
"NODE_MAJOR": "22",
|
||||
"RUST_TOOLCHAIN": "1.92.0",
|
||||
"CODEX_NPM_VERSION": "0.121.0"
|
||||
}
|
||||
},
|
||||
"runArgs": [
|
||||
"--cap-add=SYS_ADMIN",
|
||||
"--cap-add=SYS_CHROOT",
|
||||
"--cap-add=SETUID",
|
||||
"--cap-add=SETGID",
|
||||
"--cap-add=SYS_PTRACE",
|
||||
"--security-opt=seccomp=unconfined",
|
||||
"--security-opt=apparmor=unconfined",
|
||||
"--cap-add=NET_ADMIN",
|
||||
"--cap-add=NET_RAW"
|
||||
],
|
||||
"init": true,
|
||||
"updateRemoteUserUID": true,
|
||||
"remoteUser": "vscode",
|
||||
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated",
|
||||
"workspaceFolder": "/workspace",
|
||||
"mounts": [
|
||||
"source=codex-commandhistory-${devcontainerId},target=/commandhistory,type=volume",
|
||||
"source=codex-home-${devcontainerId},target=/home/vscode/.codex,type=volume",
|
||||
"source=codex-gh-${devcontainerId},target=/home/vscode/.config/gh,type=volume",
|
||||
"source=codex-cargo-registry-${devcontainerId},target=/home/vscode/.cargo/registry,type=volume",
|
||||
"source=codex-cargo-git-${devcontainerId},target=/home/vscode/.cargo/git,type=volume",
|
||||
"source=codex-rustup-${devcontainerId},target=/home/vscode/.rustup,type=volume",
|
||||
"source=${localEnv:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,readonly"
|
||||
],
|
||||
"containerEnv": {
|
||||
"RUST_BACKTRACE": "1",
|
||||
"CODEX_UNSAFE_ALLOW_NO_SANDBOX": "1",
|
||||
"CODEX_ENABLE_FIREWALL": "1",
|
||||
"CODEX_INCLUDE_GITHUB_META_RANGES": "1",
|
||||
"OPENAI_ALLOWED_DOMAINS": "api.openai.com auth.openai.com github.com api.github.com codeload.github.com raw.githubusercontent.com objects.githubusercontent.com crates.io index.crates.io static.crates.io static.rust-lang.org registry.npmjs.org pypi.org files.pythonhosted.org",
|
||||
"CARGO_TARGET_DIR": "/workspace/.cache/cargo-target",
|
||||
"GIT_CONFIG_GLOBAL": "/home/vscode/.gitconfig.local",
|
||||
"COREPACK_ENABLE_DOWNLOAD_PROMPT": "0",
|
||||
"PYTHONDONTWRITEBYTECODE": "1",
|
||||
"PIP_DISABLE_PIP_VERSION_CHECK": "1"
|
||||
},
|
||||
"remoteEnv": {
|
||||
"OPENAI_API_KEY": "${localEnv:OPENAI_API_KEY}"
|
||||
},
|
||||
"postCreateCommand": "python3 /opt/post_install.py",
|
||||
"postStartCommand": "bash /opt/post_start.sh",
|
||||
"waitFor": "postStartCommand",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"terminal.integrated.defaultProfile.linux": "zsh",
|
||||
"terminal.integrated.profiles.linux": {
|
||||
"bash": {
|
||||
"path": "bash",
|
||||
"icon": "terminal-bash"
|
||||
},
|
||||
"zsh": {
|
||||
"path": "zsh"
|
||||
}
|
||||
},
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.insertFinalNewline": true,
|
||||
"files.trimFinalNewlines": true
|
||||
},
|
||||
"extensions": [
|
||||
"openai.chatgpt",
|
||||
"rust-lang.rust-analyzer",
|
||||
"tamasfe.even-better-toml",
|
||||
"vadimcn.vscode-lldb",
|
||||
"ms-azuretools.vscode-docker"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
170
reference/openai-codex/.devcontainer/init-firewall.sh
Normal file
170
reference/openai-codex/.devcontainer/init-firewall.sh
Normal file
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
allowed_domains_file="/etc/codex/allowed_domains.txt"
|
||||
include_github_meta_ranges="${CODEX_INCLUDE_GITHUB_META_RANGES:-1}"
|
||||
|
||||
if [ -f "$allowed_domains_file" ]; then
|
||||
mapfile -t allowed_domains < <(sed '/^\s*#/d;/^\s*$/d' "$allowed_domains_file")
|
||||
else
|
||||
allowed_domains=("api.openai.com")
|
||||
fi
|
||||
|
||||
if [ "${#allowed_domains[@]}" -eq 0 ]; then
|
||||
echo "ERROR: No allowed domains configured"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
add_ipv4_cidr_to_allowlist() {
|
||||
local source="$1"
|
||||
local cidr="$2"
|
||||
|
||||
if [[ ! "$cidr" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}/[0-9]{1,2}$ ]]; then
|
||||
echo "ERROR: Invalid ${source} CIDR range: $cidr"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ipset add allowed-domains "$cidr" -exist
|
||||
}
|
||||
|
||||
configure_ipv6_default_deny() {
|
||||
if ! command -v ip6tables >/dev/null 2>&1; then
|
||||
echo "ERROR: ip6tables is required to enforce IPv6 default-deny policy"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ip6tables -F
|
||||
ip6tables -X
|
||||
ip6tables -t mangle -F
|
||||
ip6tables -t mangle -X
|
||||
ip6tables -t nat -F 2>/dev/null || true
|
||||
ip6tables -t nat -X 2>/dev/null || true
|
||||
|
||||
ip6tables -A INPUT -i lo -j ACCEPT
|
||||
ip6tables -A OUTPUT -o lo -j ACCEPT
|
||||
ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
ip6tables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
ip6tables -P INPUT DROP
|
||||
ip6tables -P FORWARD DROP
|
||||
ip6tables -P OUTPUT DROP
|
||||
|
||||
echo "IPv6 firewall policy configured (default-deny)"
|
||||
}
|
||||
|
||||
# Preserve docker-managed DNS NAT rules before clearing tables.
|
||||
docker_dns_rules="$(iptables-save -t nat | grep "127\\.0\\.0\\.11" || true)"
|
||||
|
||||
iptables -F
|
||||
iptables -X
|
||||
iptables -t nat -F
|
||||
iptables -t nat -X
|
||||
iptables -t mangle -F
|
||||
iptables -t mangle -X
|
||||
ipset destroy allowed-domains 2>/dev/null || true
|
||||
|
||||
if [ -n "$docker_dns_rules" ]; then
|
||||
echo "Restoring Docker DNS NAT rules"
|
||||
iptables -t nat -N DOCKER_OUTPUT 2>/dev/null || true
|
||||
iptables -t nat -N DOCKER_POSTROUTING 2>/dev/null || true
|
||||
while IFS= read -r rule; do
|
||||
[ -z "$rule" ] && continue
|
||||
iptables -t nat $rule
|
||||
done <<< "$docker_dns_rules"
|
||||
fi
|
||||
|
||||
# Allow DNS resolution and localhost communication.
|
||||
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
|
||||
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
|
||||
iptables -A INPUT -p udp --sport 53 -j ACCEPT
|
||||
iptables -A INPUT -p tcp --sport 53 -j ACCEPT
|
||||
iptables -A INPUT -i lo -j ACCEPT
|
||||
iptables -A OUTPUT -o lo -j ACCEPT
|
||||
|
||||
ipset create allowed-domains hash:net
|
||||
|
||||
for domain in "${allowed_domains[@]}"; do
|
||||
echo "Resolving $domain"
|
||||
ips="$(dig +short A "$domain" | sed '/^\s*$/d')"
|
||||
if [ -z "$ips" ]; then
|
||||
echo "ERROR: Failed to resolve $domain"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
while IFS= read -r ip; do
|
||||
if [[ ! "$ip" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]]; then
|
||||
echo "ERROR: Invalid IPv4 address from DNS for $domain: $ip"
|
||||
exit 1
|
||||
fi
|
||||
ipset add allowed-domains "$ip" -exist
|
||||
done <<< "$ips"
|
||||
done
|
||||
|
||||
if [ "$include_github_meta_ranges" = "1" ]; then
|
||||
echo "Fetching GitHub meta ranges"
|
||||
github_meta="$(curl -fsSL --connect-timeout 10 https://api.github.com/meta)"
|
||||
|
||||
if ! echo "$github_meta" | jq -e '.web and .api and .git' >/dev/null; then
|
||||
echo "ERROR: GitHub meta response missing expected fields"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
while IFS= read -r cidr; do
|
||||
[ -z "$cidr" ] && continue
|
||||
if [[ "$cidr" == *:* ]]; then
|
||||
# Current policy enforces IPv4-only ipset entries.
|
||||
continue
|
||||
fi
|
||||
add_ipv4_cidr_to_allowlist "GitHub" "$cidr"
|
||||
done < <(echo "$github_meta" | jq -r '((.web // []) + (.api // []) + (.git // []))[]' | sort -u)
|
||||
fi
|
||||
|
||||
host_ip="$(ip route | awk '/default/ {print $3; exit}')"
|
||||
if [ -z "$host_ip" ]; then
|
||||
echo "ERROR: Failed to detect host IP"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
host_network="$(echo "$host_ip" | sed 's/\.[0-9]*$/.0\/24/')"
|
||||
iptables -A INPUT -s "$host_network" -j ACCEPT
|
||||
iptables -A OUTPUT -d "$host_network" -j ACCEPT
|
||||
|
||||
iptables -P INPUT DROP
|
||||
iptables -P FORWARD DROP
|
||||
iptables -P OUTPUT DROP
|
||||
|
||||
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
iptables -A OUTPUT -m set --match-set allowed-domains dst -j ACCEPT
|
||||
|
||||
# Reject rather than silently drop to make policy failures obvious.
|
||||
iptables -A INPUT -j REJECT --reject-with icmp-admin-prohibited
|
||||
iptables -A OUTPUT -j REJECT --reject-with icmp-admin-prohibited
|
||||
iptables -A FORWARD -j REJECT --reject-with icmp-admin-prohibited
|
||||
|
||||
configure_ipv6_default_deny
|
||||
|
||||
echo "Firewall configuration complete"
|
||||
|
||||
if curl --connect-timeout 5 https://example.com >/dev/null 2>&1; then
|
||||
echo "ERROR: Firewall verification failed - was able to reach https://example.com"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! curl --connect-timeout 5 https://api.openai.com >/dev/null 2>&1; then
|
||||
echo "ERROR: Firewall verification failed - unable to reach https://api.openai.com"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$include_github_meta_ranges" = "1" ] && ! curl --connect-timeout 5 https://api.github.com/zen >/dev/null 2>&1; then
|
||||
echo "ERROR: Firewall verification failed - unable to reach https://api.github.com"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if curl --connect-timeout 5 -6 https://example.com >/dev/null 2>&1; then
|
||||
echo "ERROR: Firewall verification failed - was able to reach https://example.com over IPv6"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Firewall verification passed"
|
||||
36
reference/openai-codex/.devcontainer/post-start.sh
Normal file
36
reference/openai-codex/.devcontainer/post-start.sh
Normal file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${CODEX_ENABLE_FIREWALL:-1}" != "1" ]; then
|
||||
echo "[devcontainer] Firewall mode: permissive (CODEX_ENABLE_FIREWALL=${CODEX_ENABLE_FIREWALL:-unset})."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "[devcontainer] Firewall mode: strict"
|
||||
|
||||
domains_raw="${OPENAI_ALLOWED_DOMAINS:-api.openai.com}"
|
||||
mapfile -t domains < <(printf '%s\n' "$domains_raw" | tr ', ' '\n\n' | sed '/^$/d' | sort -u)
|
||||
|
||||
if [ "${#domains[@]}" -eq 0 ]; then
|
||||
echo "[devcontainer] No allowed domains configured."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tmp_file="$(mktemp)"
|
||||
for domain in "${domains[@]}"; do
|
||||
if [[ ! "$domain" =~ ^[a-zA-Z0-9][a-zA-Z0-9.-]*\.[a-zA-Z]{2,}$ ]]; then
|
||||
echo "[devcontainer] Invalid domain in OPENAI_ALLOWED_DOMAINS: $domain"
|
||||
rm -f "$tmp_file"
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$domain" >> "$tmp_file"
|
||||
done
|
||||
|
||||
sudo install -d -m 0755 /etc/codex
|
||||
sudo cp "$tmp_file" /etc/codex/allowed_domains.txt
|
||||
sudo chown root:root /etc/codex/allowed_domains.txt
|
||||
sudo chmod 0444 /etc/codex/allowed_domains.txt
|
||||
rm -f "$tmp_file"
|
||||
|
||||
echo "[devcontainer] Applying firewall policy for domains: ${domains[*]}"
|
||||
sudo --preserve-env=CODEX_INCLUDE_GITHUB_META_RANGES /usr/local/bin/init-firewall.sh
|
||||
113
reference/openai-codex/.devcontainer/post_install.py
Normal file
113
reference/openai-codex/.devcontainer/post_install.py
Normal file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Post-install configuration for the Codex devcontainer."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def ensure_history_files() -> None:
|
||||
command_history_dir = Path("/commandhistory")
|
||||
command_history_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for filename in (".bash_history", ".zsh_history"):
|
||||
(command_history_dir / filename).touch(exist_ok=True)
|
||||
|
||||
|
||||
def fix_directory_ownership() -> None:
|
||||
uid = os.getuid()
|
||||
gid = os.getgid()
|
||||
|
||||
paths = [
|
||||
Path.home() / ".codex",
|
||||
Path.home() / ".config" / "gh",
|
||||
Path.home() / ".cargo",
|
||||
Path.home() / ".rustup",
|
||||
Path("/commandhistory"),
|
||||
]
|
||||
|
||||
for path in paths:
|
||||
if not path.exists():
|
||||
continue
|
||||
|
||||
stat_info = path.stat()
|
||||
if stat_info.st_uid == uid and stat_info.st_gid == gid:
|
||||
continue
|
||||
|
||||
try:
|
||||
subprocess.run(
|
||||
["sudo", "chown", "-R", f"{uid}:{gid}", str(path)],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
print(f"[post_install] fixed ownership: {path}", file=sys.stderr)
|
||||
except subprocess.CalledProcessError as err:
|
||||
print(
|
||||
f"[post_install] warning: could not fix ownership of {path}: {err.stderr.strip()}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
|
||||
def setup_git_config() -> None:
|
||||
home = Path.home()
|
||||
host_gitconfig = home / ".gitconfig"
|
||||
local_gitconfig = home / ".gitconfig.local"
|
||||
gitignore_global = home / ".gitignore_global"
|
||||
|
||||
gitignore_global.write_text(
|
||||
"""# Codex
|
||||
.codex/
|
||||
|
||||
# Rust
|
||||
/target/
|
||||
|
||||
# Node
|
||||
node_modules/
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Editors
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
include_line = (
|
||||
f"[include]\n path = {host_gitconfig}\n\n" if host_gitconfig.exists() else ""
|
||||
)
|
||||
|
||||
local_gitconfig.write_text(
|
||||
f"""# Container-local git configuration
|
||||
{include_line}[core]
|
||||
excludesfile = {gitignore_global}
|
||||
|
||||
[merge]
|
||||
conflictstyle = diff3
|
||||
|
||||
[diff]
|
||||
colorMoved = default
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("[post_install] configuring devcontainer...", file=sys.stderr)
|
||||
ensure_history_files()
|
||||
fix_directory_ownership()
|
||||
setup_git_config()
|
||||
print("[post_install] complete", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user