Compare commits

..

1 Commits

Author SHA1 Message Date
Michael Bolin
5e87e4ba5e Publish stable DotSlash assets for argument-comment lint 2026-03-18 15:57:29 -07:00
12 changed files with 178 additions and 329 deletions

View File

@@ -0,0 +1,24 @@
{
"outputs": {
"argument-comment-lint": {
"platforms": {
"macos-aarch64": {
"regex": "^libargument_comment_lint@nightly-2025-09-18-aarch64-apple-darwin\\.dylib\\.zst$",
"path": "libargument_comment_lint@nightly-2025-09-18-aarch64-apple-darwin.dylib"
},
"linux-x86_64": {
"regex": "^libargument_comment_lint@nightly-2025-09-18-x86_64-unknown-linux-gnu\\.so\\.zst$",
"path": "libargument_comment_lint@nightly-2025-09-18-x86_64-unknown-linux-gnu.so"
},
"linux-aarch64": {
"regex": "^libargument_comment_lint@nightly-2025-09-18-aarch64-unknown-linux-gnu\\.so\\.zst$",
"path": "libargument_comment_lint@nightly-2025-09-18-aarch64-unknown-linux-gnu.so"
},
"windows-x86_64": {
"regex": "^argument_comment_lint@nightly-2025-09-18-x86_64-pc-windows-msvc\\.dll\\.zst$",
"path": "argument_comment_lint@nightly-2025-09-18-x86_64-pc-windows-msvc.dll"
}
}
}
}
}

View File

@@ -0,0 +1,88 @@
name: rust-release-argument-comment-lint
on:
workflow_call:
inputs:
publish:
required: true
type: boolean
jobs:
skip:
if: ${{ !inputs.publish }}
runs-on: ubuntu-latest
steps:
- run: echo "Skipping argument-comment-lint release assets for prerelease tag"
build:
if: ${{ inputs.publish }}
name: Build - ${{ matrix.runner }} - ${{ matrix.target }}
runs-on: ${{ matrix.runs_on || matrix.runner }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- runner: macos-15-xlarge
target: aarch64-apple-darwin
lib_name: libargument_comment_lint@nightly-2025-09-18-aarch64-apple-darwin.dylib
- runner: ubuntu-24.04
target: x86_64-unknown-linux-gnu
lib_name: libargument_comment_lint@nightly-2025-09-18-x86_64-unknown-linux-gnu.so
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
lib_name: libargument_comment_lint@nightly-2025-09-18-aarch64-unknown-linux-gnu.so
- runner: windows-x64
target: x86_64-pc-windows-msvc
lib_name: argument_comment_lint@nightly-2025-09-18-x86_64-pc-windows-msvc.dll
runs_on:
group: codex-runners
labels: codex-windows-x64
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@1.93.0
with:
toolchain: nightly-2025-09-18
targets: ${{ matrix.target }}
components: llvm-tools-preview, rustc-dev, rust-src
- name: Install dylint-link
shell: bash
run: cargo install --locked dylint-link
- name: Cargo build
working-directory: tools/argument-comment-lint
shell: bash
run: cargo build --release --target ${{ matrix.target }}
- if: ${{ runner.os == 'Windows' }}
name: Install DotSlash
uses: facebook/install-dotslash@v2
- name: Stage artifact
shell: bash
run: |
dest="dist/argument-comment-lint/${{ matrix.target }}"
mkdir -p "$dest"
cp "tools/argument-comment-lint/target/${{ matrix.target }}/release/${{ matrix.lib_name }}" \
"$dest/${{ matrix.lib_name }}"
- if: ${{ runner.os != 'Windows' }}
name: Compress artifact
shell: bash
run: |
zstd -T0 -19 --rm "dist/argument-comment-lint/${{ matrix.target }}/${{ matrix.lib_name }}"
- if: ${{ runner.os == 'Windows' }}
name: Compress artifact
shell: bash
run: |
./.github/workflows/zstd -T0 -19 --rm "dist/argument-comment-lint/${{ matrix.target }}/${{ matrix.lib_name }}"
- uses: actions/upload-artifact@v7
with:
name: argument-comment-lint-${{ matrix.target }}
path: dist/argument-comment-lint/${{ matrix.target }}/*

View File

@@ -380,11 +380,19 @@ jobs:
publish: true
secrets: inherit
argument-comment-lint-release-assets:
name: argument-comment-lint release assets
needs: tag-check
uses: ./.github/workflows/rust-release-argument-comment-lint.yml
with:
publish: ${{ !contains(github.ref_name, '-') }}
release:
needs:
- build
- build-windows
- shell-tool-mcp
- argument-comment-lint-release-assets
name: release
runs-on: ubuntu-latest
permissions:
@@ -521,6 +529,14 @@ jobs:
tag: ${{ github.ref_name }}
config: .github/dotslash-config.json
- if: ${{ !contains(steps.release_name.outputs.name, '-') }}
uses: facebook/dotslash-publish-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag: ${{ github.ref_name }}
config: .github/dotslash-argument-comment-lint-config.json
- name: Trigger developers.openai.com deploy
# Only trigger the deploy if the release is not a pre-release.
# The deploy is used to update the developers.openai.com website with the new config schema json file.

View File

@@ -898,14 +898,6 @@
],
"type": "object"
},
"NetworkDomainPermissionToml": {
"enum": [
"allow",
"deny",
"none"
],
"type": "string"
},
"NetworkModeSchema": {
"enum": [
"limited",
@@ -919,20 +911,32 @@
"allow_local_binding": {
"type": "boolean"
},
"allow_unix_sockets": {
"items": {
"type": "string"
},
"type": "array"
},
"allow_upstream_proxy": {
"type": "boolean"
},
"allowed_domains": {
"items": {
"type": "string"
},
"type": "array"
},
"dangerously_allow_all_unix_sockets": {
"type": "boolean"
},
"dangerously_allow_non_loopback_proxy": {
"type": "boolean"
},
"domains": {
"additionalProperties": {
"$ref": "#/definitions/NetworkDomainPermissionToml"
"denied_domains": {
"items": {
"type": "string"
},
"type": "object"
"type": "array"
},
"enable_socks5": {
"type": "boolean"
@@ -951,23 +955,10 @@
},
"socks_url": {
"type": "string"
},
"unix_sockets": {
"additionalProperties": {
"$ref": "#/definitions/NetworkUnixSocketPermissionToml"
},
"type": "object"
}
},
"type": "object"
},
"NetworkUnixSocketPermissionToml": {
"enum": [
"allow",
"none"
],
"type": "string"
},
"Notice": {
"description": "Settings for notices we display to users via the tui and app-server clients (primarily the Codex IDE extension). NOTE: these are different from notifications - notices are warnings, NUX screens, acknowledgements, etc.",
"properties": {

View File

@@ -280,9 +280,7 @@ enabled = true
proxy_url = "http://127.0.0.1:43128"
enable_socks5 = false
allow_upstream_proxy = false
[permissions.workspace.network.domains]
"openai.com" = "allow"
allowed_domains = ["openai.com"]
"#;
let cfg: ConfigToml =
toml::from_str(toml).expect("TOML deserialization should succeed for permissions profiles");
@@ -319,11 +317,9 @@ allow_upstream_proxy = false
dangerously_allow_non_loopback_proxy: None,
dangerously_allow_all_unix_sockets: None,
mode: None,
domains: Some(BTreeMap::from([(
"openai.com".to_string(),
NetworkDomainPermissionToml::Allow,
)])),
unix_sockets: None,
allowed_domains: Some(vec!["openai.com".to_string()]),
denied_domains: None,
allow_unix_sockets: None,
allow_local_binding: None,
}),
},
@@ -399,10 +395,7 @@ fn permissions_profiles_network_disabled_by_default_does_not_start_proxy() -> st
)]),
}),
network: Some(NetworkToml {
domains: Some(BTreeMap::from([(
"openai.com".to_string(),
NetworkDomainPermissionToml::Allow,
)])),
allowed_domains: Some(vec!["openai.com".to_string()]),
..Default::default()
}),
},

View File

@@ -123,9 +123,7 @@ pub use network_proxy_spec::NetworkProxySpec;
pub use network_proxy_spec::StartedNetworkProxy;
pub use permissions::FilesystemPermissionToml;
pub use permissions::FilesystemPermissionsToml;
pub use permissions::NetworkDomainPermissionToml;
pub use permissions::NetworkToml;
pub use permissions::NetworkUnixSocketPermissionToml;
pub use permissions::PermissionProfileToml;
pub use permissions::PermissionsToml;
pub(crate) use permissions::resolve_permission_profile;

View File

@@ -7,7 +7,6 @@ use std::path::PathBuf;
use codex_network_proxy::NetworkMode;
use codex_network_proxy::NetworkProxyConfig;
use codex_network_proxy::normalize_host;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
@@ -70,32 +69,12 @@ pub struct NetworkToml {
pub dangerously_allow_all_unix_sockets: Option<bool>,
#[schemars(with = "Option<NetworkModeSchema>")]
pub mode: Option<NetworkMode>,
pub domains: Option<BTreeMap<String, NetworkDomainPermissionToml>>,
pub unix_sockets: Option<BTreeMap<String, NetworkUnixSocketPermissionToml>>,
pub allowed_domains: Option<Vec<String>>,
pub denied_domains: Option<Vec<String>>,
pub allow_unix_sockets: Option<Vec<String>>,
pub allow_local_binding: Option<bool>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum NetworkDomainPermissionToml {
Allow,
Deny,
None,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum NetworkUnixSocketPermissionToml {
Allow,
None,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ResolvedNetworkDomainEntry {
pub domain: String,
pub permission: NetworkDomainPermissionToml,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "lowercase")]
enum NetworkModeSchema {
@@ -135,20 +114,14 @@ impl NetworkToml {
if let Some(mode) = self.mode {
config.network.mode = mode;
}
if self.domains.is_some() {
let resolved_domain_entries = self.resolved_domain_entries();
let mut allowed_domains = Vec::new();
let mut denied_domains = Vec::new();
apply_resolved_domain_entries(
&mut allowed_domains,
&mut denied_domains,
&resolved_domain_entries,
);
config.network.allowed_domains = allowed_domains;
config.network.denied_domains = denied_domains;
if let Some(allowed_domains) = self.allowed_domains.as_ref() {
config.network.allowed_domains = allowed_domains.clone();
}
if let Some(allow_unix_sockets) = self.compile_unix_socket_permissions() {
config.network.allow_unix_sockets = allow_unix_sockets;
if let Some(denied_domains) = self.denied_domains.as_ref() {
config.network.denied_domains = denied_domains.clone();
}
if let Some(allow_unix_sockets) = self.allow_unix_sockets.as_ref() {
config.network.allow_unix_sockets = allow_unix_sockets.clone();
}
if let Some(allow_local_binding) = self.allow_local_binding {
config.network.allow_local_binding = allow_local_binding;
@@ -160,98 +133,6 @@ impl NetworkToml {
self.apply_to_network_proxy_config(&mut config);
config
}
pub(crate) fn resolved_domain_entries(&self) -> Vec<ResolvedNetworkDomainEntry> {
self.domains
.as_ref()
.map(|domains| {
domains
.iter()
.map(|(domain, permission)| ResolvedNetworkDomainEntry {
domain: domain.clone(),
permission: *permission,
})
.collect()
})
.unwrap_or_default()
}
pub(crate) fn compile_unix_socket_permissions(&self) -> Option<Vec<String>> {
self.unix_sockets.as_ref().map(|unix_sockets| {
unix_sockets
.iter()
.filter_map(|(path, permission)| {
(*permission == NetworkUnixSocketPermissionToml::Allow).then(|| path.clone())
})
.collect()
})
}
pub(crate) fn apply_domain_permissions_to(
&self,
allowed_domains: &mut Vec<String>,
denied_domains: &mut Vec<String>,
) {
if self.domains.is_some() {
let resolved_domain_entries = self.resolved_domain_entries();
apply_resolved_domain_entries(
allowed_domains,
denied_domains,
&resolved_domain_entries,
);
}
}
pub(crate) fn apply_unix_socket_permissions_to(&self, allow_unix_sockets: &mut Vec<String>) {
if let Some(unix_sockets) = self.unix_sockets.as_ref() {
apply_unix_socket_permissions(allow_unix_sockets, unix_sockets);
}
}
}
fn apply_resolved_domain_entries(
allowed_domains: &mut Vec<String>,
denied_domains: &mut Vec<String>,
resolved_domain_entries: &[ResolvedNetworkDomainEntry],
) {
for ResolvedNetworkDomainEntry { domain, permission } in resolved_domain_entries {
match permission {
NetworkDomainPermissionToml::Allow => {
remove_network_domain(denied_domains, domain);
remove_network_domain(allowed_domains, domain);
allowed_domains.push(domain.clone());
}
NetworkDomainPermissionToml::Deny => {
remove_network_domain(allowed_domains, domain);
remove_network_domain(denied_domains, domain);
denied_domains.push(domain.clone());
}
NetworkDomainPermissionToml::None => {
remove_network_domain(allowed_domains, domain);
remove_network_domain(denied_domains, domain);
}
}
}
allowed_domains.sort_unstable();
denied_domains.sort_unstable();
}
fn apply_unix_socket_permissions(
allow_unix_sockets: &mut Vec<String>,
unix_sockets: &BTreeMap<String, NetworkUnixSocketPermissionToml>,
) {
for (socket_path, permission) in unix_sockets {
allow_unix_sockets.retain(|entry| entry != socket_path);
if *permission == NetworkUnixSocketPermissionToml::Allow {
allow_unix_sockets.push(socket_path.clone());
}
}
allow_unix_sockets.sort_unstable();
}
fn remove_network_domain(domains: &mut Vec<String>, domain: &str) {
let normalized_domain = normalize_host(domain);
domains.retain(|entry| normalize_host(entry) != normalized_domain);
}
pub(crate) fn network_proxy_config_from_profile_network(

View File

@@ -7,76 +7,3 @@ fn normalize_absolute_path_for_platform_simplifies_windows_verbatim_paths() {
normalize_absolute_path_for_platform(r"\\?\D:\c\x\worktrees\2508\swift-base", true);
assert_eq!(parsed, PathBuf::from(r"D:\c\x\worktrees\2508\swift-base"));
}
#[test]
fn resolved_domain_entries_preserve_declared_rules() {
let network = NetworkToml {
domains: Some(BTreeMap::from([
(
"example.com".to_string(),
NetworkDomainPermissionToml::Allow,
),
(
"blocked.example.com".to_string(),
NetworkDomainPermissionToml::Deny,
),
(
"clear.example.com".to_string(),
NetworkDomainPermissionToml::None,
),
])),
..Default::default()
};
assert_eq!(
network.resolved_domain_entries(),
vec![
ResolvedNetworkDomainEntry {
domain: "blocked.example.com".to_string(),
permission: NetworkDomainPermissionToml::Deny,
},
ResolvedNetworkDomainEntry {
domain: "clear.example.com".to_string(),
permission: NetworkDomainPermissionToml::None,
},
ResolvedNetworkDomainEntry {
domain: "example.com".to_string(),
permission: NetworkDomainPermissionToml::Allow,
},
]
);
}
#[test]
fn apply_domain_permissions_to_respects_none_entries_case_insensitively() {
let network = NetworkToml {
domains: Some(BTreeMap::from([(
"EXAMPLE.com".to_string(),
NetworkDomainPermissionToml::None,
)])),
..Default::default()
};
let mut allowed_domains = vec!["example.com".to_string()];
let mut denied_domains = vec!["Example.com".to_string()];
network.apply_domain_permissions_to(&mut allowed_domains, &mut denied_domains);
assert_eq!(allowed_domains, Vec::<String>::new());
assert_eq!(denied_domains, Vec::<String>::new());
}
#[test]
fn apply_to_network_proxy_config_clears_domains_for_empty_table() {
let network = NetworkToml {
domains: Some(BTreeMap::new()),
..Default::default()
};
let mut config = NetworkProxyConfig::default();
config.network.allowed_domains = vec!["example.com".to_string()];
config.network.denied_domains = vec!["blocked.example.com".to_string()];
network.apply_to_network_proxy_config(&mut config);
assert_eq!(config.network.allowed_domains, Vec::<String>::new());
assert_eq!(config.network.denied_domains, Vec::<String>::new());
}

View File

@@ -150,14 +150,14 @@ fn apply_network_constraints(network: NetworkToml, constraints: &mut NetworkProx
if let Some(dangerously_allow_all_unix_sockets) = network.dangerously_allow_all_unix_sockets {
constraints.dangerously_allow_all_unix_sockets = Some(dangerously_allow_all_unix_sockets);
}
if network.domains.is_some() {
let allowed_domains = constraints.allowed_domains.get_or_insert_default();
let denied_domains = constraints.denied_domains.get_or_insert_default();
network.apply_domain_permissions_to(allowed_domains, denied_domains);
if let Some(allowed_domains) = network.allowed_domains {
constraints.allowed_domains = Some(allowed_domains);
}
if network.unix_sockets.is_some() {
let allow_unix_sockets = constraints.allow_unix_sockets.get_or_insert_default();
network.apply_unix_socket_permissions_to(allow_unix_sockets);
if let Some(denied_domains) = network.denied_domains {
constraints.denied_domains = Some(denied_domains);
}
if let Some(allow_unix_sockets) = network.allow_unix_sockets {
constraints.allow_unix_sockets = Some(allow_unix_sockets);
}
if let Some(allow_local_binding) = network.allow_local_binding {
constraints.allow_local_binding = Some(allow_local_binding);
@@ -191,18 +191,7 @@ fn selected_network_from_tables(parsed: NetworkTablesToml) -> Result<Option<Netw
}
fn apply_network_tables(config: &mut NetworkProxyConfig, parsed: NetworkTablesToml) -> Result<()> {
if let Some(mut network) = selected_network_from_tables(parsed)? {
if network.domains.is_some() {
network.apply_domain_permissions_to(
&mut config.network.allowed_domains,
&mut config.network.denied_domains,
);
}
if network.unix_sockets.is_some() {
network.apply_unix_socket_permissions_to(&mut config.network.allow_unix_sockets);
}
network.domains = None;
network.unix_sockets = None;
if let Some(network) = selected_network_from_tables(parsed)? {
network.apply_to_network_proxy_config(config);
}
Ok(())

View File

@@ -12,9 +12,7 @@ fn higher_precedence_profile_network_beats_lower_profile_network() {
default_permissions = "workspace"
[permissions.workspace.network]
[permissions.workspace.network.domains]
"lower.example.com" = "allow"
allowed_domains = ["lower.example.com"]
"#,
)
.expect("lower layer should parse");
@@ -23,9 +21,7 @@ default_permissions = "workspace"
default_permissions = "workspace"
[permissions.workspace.network]
[permissions.workspace.network.domains]
"higher.example.com" = "allow"
allowed_domains = ["higher.example.com"]
"#,
)
.expect("higher layer should parse");
@@ -42,60 +38,7 @@ default_permissions = "workspace"
)
.expect("higher layer should apply");
assert_eq!(
config.network.allowed_domains,
vec![
"higher.example.com".to_string(),
"lower.example.com".to_string()
]
);
}
#[test]
fn higher_precedence_network_domain_none_removes_inherited_entry() {
let lower_network: toml::Value = toml::from_str(
r#"
default_permissions = "workspace"
[permissions.workspace.network.domains]
"lower.example.com" = "allow"
"blocked.example.com" = "deny"
"keep.example.com" = "allow"
"#,
)
.expect("lower layer should parse");
let higher_network: toml::Value = toml::from_str(
r#"
default_permissions = "workspace"
[permissions.workspace.network.domains]
"lower.example.com" = "none"
"blocked.example.com" = "none"
"extra.example.com" = "allow"
"#,
)
.expect("higher layer should parse");
let mut config = NetworkProxyConfig::default();
apply_network_tables(
&mut config,
network_tables_from_toml(&lower_network).expect("lower layer should deserialize"),
)
.expect("lower layer should apply");
apply_network_tables(
&mut config,
network_tables_from_toml(&higher_network).expect("higher layer should deserialize"),
)
.expect("higher layer should apply");
assert_eq!(
config.network.allowed_domains,
vec![
"extra.example.com".to_string(),
"keep.example.com".to_string()
]
);
assert!(config.network.denied_domains.is_empty());
assert_eq!(config.network.allowed_domains, vec!["higher.example.com"]);
}
#[test]

View File

@@ -40,13 +40,9 @@ mitm = false
# Hosts must match the allowlist (unless denied).
# Use exact hosts or scoped wildcards like `*.openai.com` or `**.openai.com`.
# The global `*` wildcard is rejected.
# If no domain entries are set to `allow`, the proxy blocks requests until an allowlist is configured.
[permissions.workspace.network.domains]
"*.openai.com" = "allow"
"localhost" = "allow"
"127.0.0.1" = "allow"
"::1" = "allow"
"evil.example" = "deny"
# If `allowed_domains` is empty, the proxy blocks requests until an allowlist is configured.
allowed_domains = ["*.openai.com", "localhost", "127.0.0.1", "::1"]
denied_domains = ["evil.example"]
# If false, local/private networking is rejected. Explicit allowlisting of local IP literals
# (or `localhost`) is required to permit them.
@@ -54,8 +50,7 @@ mitm = false
allow_local_binding = false
# macOS-only: allows proxying to a unix socket when request includes `x-unix-socket: /path`.
[permissions.workspace.network.unix_sockets]
"/tmp/example.sock" = "allow"
allow_unix_sockets = ["/tmp/example.sock"]
# DANGEROUS (macOS-only): bypasses unix socket allowlisting and permits any
# absolute socket path from `x-unix-socket`.
dangerously_allow_all_unix_sockets = false

View File

@@ -68,6 +68,10 @@ cd tools/argument-comment-lint
cargo test
```
Stable GitHub releases also publish a DotSlash file named
`argument-comment-lint` for the prebuilt library on macOS arm64, Linux arm64,
Linux x64, and Windows x64.
Run the lint against `codex-rs` from the repo root:
```bash