opencode-codex-security 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,44 +1,58 @@
1
1
  # npm staging and human approval
2
2
 
3
- The source is public under Apache 2.0. Version `0.1.0` is live on npm under `latest`, verified November 10, 2026. The manifest retains `private: false`, public registry/access settings, a conservative `contentPolicy.class: dual-use` declaration and a root `DISCLOSURE`. Version `0.1.1` is prepared for staging; a version bump alone does not stage or publish it. GitHub previews remain available independently.
3
+ `opencode-codex-security` is public on npm under Apache 2.0. Bootstrap is complete, and the GitHub OIDC staging workflow has been exercised successfully. Every release still needs a maintainer's separate review and npm 2FA approval before it is installable. Workflow success means **staged**, not approved or published.
4
4
 
5
- ## Security boundaries
5
+ Verified November 10, 2026: `latest` points to `0.1.0`; `0.1.1` is also public under `next`, with npm provenance. Check the registry again before choosing an installation or release version:
6
6
 
7
- This utility uses Codex Security for source scans, policy drafting and static finding validation, not exploit execution or patch application. Its dual-use metadata and disclosure must persist across versions. npm forbids direct OIDC publication for dual-use packages. This repository automates **staging only**; each live version requires a maintainer's review and npm 2FA approval. Never add a direct publish command or a token fallback.
7
+ ```sh
8
+ npm view opencode-codex-security dist-tags versions --json
9
+ ```
8
10
 
9
- `Checks` builds and tests without publishing permissions. It installs locked dependencies with lifecycle scripts disabled, audits runtime dependencies, checks source/build freshness and package allowlists, and tests the exact tarball in an isolated OpenCode host. Consumer dependencies are resolved afresh and audited too. The package has no install, prepare, pack or publish lifecycle hooks. Build explicitly before packing. These checks are not a comprehensive secret scan or a guarantee that transitive dependencies are safe.
11
+ For installation, use [the README](../README.md#install-a-released-version) or [agent setup](https://github.com/strantalis/opencode-codex-security/blob/main/docs/AGENT-SETUP.md). Consumers do not need npm login, trusted-publisher settings or these release tools. GitHub previews remain available independently through [the preview release guide](RELEASING.md).
12
+
13
+ ## Stage the next release
10
14
 
11
- `npm-stage.yml` is manual, main-only and bound in code to `strantalis/opencode-codex-security`. It requires a successful current-main push run from `ci.yml` in the same repository. It requires the tested source commit to match both current main and the staging workflow commit (`GITHUB_SHA`, recorded by npm provenance). It checks public visibility, fixed filenames, dual-use manifest, SHA-256 checksums and artifact count. It promotes those exact bytes without rebuilding. The GitHub-hosted staging job has only read access and `id-token: write`, runs pinned Node/npm tooling, and never checks out or executes project code or installs project dependencies. The action is pinned to a commit. There are no npm secrets. The workflow verifies bundled npm 11.20.0 in Node 26.13.0 and fails if tooling differs.
15
+ 1. Choose a version not already live or awaiting approval. Bump `version` in both package and lockfile in a reviewed PR. Use a new patch for fixes or a full prerelease such as `0.1.2-rc.1` for testing. Preserve `contentPolicy.class: dual-use`, the license and `DISCLOSURE`.
16
+ 2. Merge only after required checks and review pass. Wait for **Checks on the new main push** to succeed. Its tested artifact is retained for 14 days. A PR run is not a substitute.
17
+ 3. Run **Actions → Stage npm release** (`npm-stage.yml`) on main with that push run's numeric ID. Choose `next` for testing/prereleases or deliberately choose `latest` for a stable release. The default is `next`; prereleases with `latest` fail closed. Both tags refer to public packages once approved.
18
+ 4. Save the returned stage ID and review it as described below. **Do not approve in CI.** The workflow has no approval job and cannot complete human 2FA.
12
19
 
13
- ## One-time bootstrap: separate maintainer action
20
+ The workflow requires the tested commit to be both current main and the workflow commit. If main moves before staging, use the successful Checks push run for the new main. If a version is already live, choose a new version instead of rerunning it. No version can be overwritten after publication.
14
21
 
15
- Bootstrap is already complete for `opencode-codex-security`; use the later-version flow below for updates. The following steps document first-time setup only. Do not run them for another package until the maintainer approves the exact npm destination, public contents and account. Confirm name availability/ownership again. Review current npm policy, archive contents, license/dependency obligations and account eligibility. npm trust requires an **existing package**; do not try to grant trust to a nonexistent package.
22
+ ## Review and approve a staged version
16
23
 
17
- Use a workstation with Node 26.13.0/npm 11.20.0 (staging needs npm 11.15.0+). Enable npm 2FA. Download all three assets from a successful current-main CI run or matching GitHub preview, inspect `build-manifest.json` and verify `SHA256SUMS` before continuing. Use the actual archive filename:
24
+ This is a separate maintainer action using an authorized npm account with 2FA. Use tooling supporting staged publishing (npm 11.15.0+); the workflow pins Node 26.13.0 and its bundled npm 11.20.0. Use the official interactive `npm login` only if sign-in is needed. Never paste or commit passwords, tokens or OTPs.
25
+
26
+ Inspect the specific stage, then download its payload into a fresh review directory:
18
27
 
19
28
  ```sh
20
- # macOS; Linux: sha256sum -c SHA256SUMS
21
- shasum -a 256 -c SHA256SUMS
22
- npm login
23
- npm whoami
24
- npm stage publish ./opencode-codex-security-0.1.0.tgz --access public --tag latest --ignore-scripts
25
29
  npm stage list opencode-codex-security
26
30
  npm stage view STAGE_ID
31
+ npm stage download STAGE_ID
27
32
  ```
28
33
 
29
- Login and bootstrap staging are separate maintainer actions, not effects of changing repository files. A 401 from `npm whoami` means the CLI is not authenticated, not that no account exists. Do not share or commit passwords, tokens or OTPs. **Staging a new name creates a public `0.0.0-stage` placeholder**, even before the first real version is approved. The staged payload itself is not a live published version. Review the stage, download and compare its tarball with the tested SHA-256, and approve only the intended version/tag. Human approval is a separate action:
34
+ Check the package name, version, intended public tag, repository, source commit and provenance. Obtain the corresponding tested CI artifact's `build-manifest.json` and `SHA256SUMS` from the run used for staging. Verify its assets (`shasum -a 256 -c SHA256SUMS` on macOS, `sha256sum -c SHA256SUMS` on Linux), then compare the **downloaded stage tarball's** SHA-256 against `archiveSha256` in that manifest. Checksums alone do not establish trust in an untrusted manifest. Review the actual archive contents and release changes before approval.
35
+
36
+ When the intended version, tag and bytes are confirmed, the maintainer performs the 2FA approval:
30
37
 
31
38
  ```sh
32
- npm stage download STAGE_ID
33
- # Compare the downloaded tarball's SHA-256 with build-manifest.json.
34
39
  npm stage approve STAGE_ID
35
40
  ```
36
41
 
37
- Approval requires 2FA and publishes that staged version. `npm stage reject STAGE_ID` also requires 2FA and discards it. Inspect any interrupted/uncertain operation with list/view before retrying; never blindly create another stage. Tags are immutable within a stage. Reject and restage if the tag was wrong. Check `npm view opencode-codex-security@0.1.0 version dist.integrity` and the intended dist-tag after approval. Do not treat a workflow success, stage ID or placeholder as a live release.
42
+ Approval publishes that exact version and applies its selected tag. To discard a stage, use `npm stage reject STAGE_ID`, also requiring 2FA. Tags are immutable within a stage: reject and restage if the selected tag was wrong. Do not try to change trust or use a token to avoid approval.
43
+
44
+ After approval, verify the specific version and intended tag in the public registry, replacing `VERSION` with the approved full version:
38
45
 
39
- ## Configure stage-only trusted publishing: separate approval
46
+ ```sh
47
+ npm view opencode-codex-security@VERSION version dist.integrity dist.attestations --json
48
+ npm view opencode-codex-security dist-tags --json
49
+ ```
40
50
 
41
- After the package exists, the maintainer must explicitly authorize and configure persistent npm trust for exactly:
51
+ Check provenance as well as the source/bytes in the build manifest. A stage ID, successful workflow or `0.0.0-stage` placeholder is not a release. An interrupted submission or approval can have an uncertain outcome: inspect stage list/view **and public registry state** before retrying. Do not blindly create another stage or assume a failed client response means nothing happened.
52
+
53
+ ## Existing trust and security boundaries
54
+
55
+ The workflow uses the following publisher identity. The npm trust grant should permit staging only:
42
56
 
43
57
  - Provider: GitHub Actions.
44
58
  - Owner: `strantalis`.
@@ -47,25 +61,22 @@ After the package exists, the maintainer must explicitly authorize and configure
47
61
  - Permission: **stage only**, never direct publishing.
48
62
  - Environment: none (this workflow does not claim an environment).
49
63
 
50
- Use npm package settings or the current `npm trust` documentation, not invented CLI flags. Do not configure a broader repository/workflow or create a long-lived token as a workaround. Newly configured trust must be used within two days or it expires. Once the staged flow is verified, select npm package protection **Require two-factor authentication and disallow tokens**. These settings require separate approval and are not made by this repository change.
64
+ The successful OIDC staging run confirms staging access for this workflow; it does not prove the full trust grant or account-wide token/2FA protection settings. Routine releases reuse the existing binding; do not repeat package bootstrap or recreate trust. If npm rejects the workflow because trust is absent, expired or mismatched, stop and have the maintainer review the exact settings using [npm's trusted-publisher documentation](https://docs.npmjs.com/trusted-publishers/). Persistent trust, package protection or account changes require separate authorization. Do not broaden the binding, disable 2FA or create a long-lived token as a workaround.
51
65
 
52
- ## Stage a later version
66
+ This utility uses Codex Security for source scans, policy drafting and static finding validation, not exploit execution or patch application. Its dual-use metadata and disclosure must persist. npm forbids direct OIDC publication for dual-use packages. This workflow uses OIDC for **staging only**; OIDC authentication does not grant human approval. Never add a direct publish command or a token fallback.
53
67
 
54
- 1. Bump `version` in both package and lockfile in a reviewed PR. Use a patch bump for fixes (`0.1.1`); use a prerelease such as `0.1.1-rc.1` with `next` for testing. Never reuse a live version. Keep the dual-use metadata and disclosure.
55
- 2. Merge only after checks pass. Wait for **Checks on the new main push** to finish successfully. Its verified artifact is retained for 14 days.
56
- 3. Run **Actions → Stage npm release** on main with that run's numeric ID. Choose `next` for prereleases/testing or deliberately choose `latest` for a stable release. The default is `next`; prereleases with `latest` fail closed.
57
- 4. Review the returned stage ID, version, tag, source and tarball hash using the human steps above. Approve with 2FA only when ready for public installation. There is no automated approval job.
68
+ `Checks` builds and tests without publishing permissions. It installs locked dependencies with lifecycle scripts disabled, audits runtime dependencies, checks source/build freshness and package allowlists, and tests the exact tarball in an isolated OpenCode host. Consumer dependencies are resolved afresh and audited too. The package has no install, prepare, pack or publish lifecycle hooks. Build explicitly before packing. These checks are not a comprehensive secret scan or a guarantee that transitive dependencies are safe.
58
69
 
59
- If main moves, the artifact expires, trust is absent/expired, or registry permissions fail, fix the specific prerequisite and use a fresh current-main successful run. Do not weaken checks or fall back to tokens. A failure after submitting to npm can have an uncertain outcome: inspect stage list/view before retrying. A version cannot be overwritten after publication; use a new version for corrections.
70
+ `npm-stage.yml` is manual, main-only and bound in code to this repository. It verifies a successful current-main push run from `ci.yml`, public visibility, fixed filenames, dual-use manifest, SHA-256 checksums and artifact count. It promotes those exact bytes without rebuilding. The tested source commit must match `GITHUB_SHA`, recorded by npm provenance. The GitHub-hosted staging job has read permissions plus `id-token: write`, uses a commit-pinned setup action and verifies its pinned Node/npm tooling. It never checks out or executes project code or installs project dependencies. There are no npm secrets.
60
71
 
61
72
  ## Limits and references
62
73
 
63
- Provenance is requested for public source/package with matching repository metadata. Review provenance after promotion: it identifies the staging workflow, while `build-manifest.json` links the bytes to the independently verified build commit/run. Native Desktop/platform testing and a complete real SDK assessment remain separate from mocked CI. Use only repositories and workplace devices authorized for scanning and data transfer.
74
+ Provenance identifies the staging workflow; `build-manifest.json` links the bytes to the independently tested build commit/run. Native Desktop/platform testing and a complete real SDK assessment remain separate from mocked CI. Use only repositories and workplace devices authorized for scanning and data transfer.
64
75
 
65
76
  - [npm stage CLI](https://docs.npmjs.com/cli/v11/commands/npm-stage/)
66
- - [npm staged publishing and bootstrap](https://docs.npmjs.com/staged-publishing/)
77
+ - [npm staged publishing](https://docs.npmjs.com/staged-publishing/)
67
78
  - [npm trusted publishers](https://docs.npmjs.com/trusted-publishers/)
68
79
  - [npm trust prerequisites](https://docs.npmjs.com/cli/v11/commands/npm-trust/)
69
80
  - [npm dual-use policy](https://docs.npmjs.com/policies/dual-use/)
70
81
 
71
- Verified November 10, 2026; recheck before setup. Installed metadata identifies Codex Security 0.9.1 as Apache-2.0, OpenCode plugin/Solid as MIT, and OpenTUI native binaries as MIT AND LGPL-2.1-or-later. Dependencies stay external; this is not comprehensive legal clearance.
82
+ Verified November 10, 2026; recheck before release. Installed metadata identifies Codex Security 0.9.1 as Apache-2.0, OpenCode plugin/Solid as MIT, and OpenTUI native binaries as MIT AND LGPL-2.1-or-later. Dependencies stay external; this is not comprehensive legal clearance.
package/docs/RELEASING.md CHANGED
@@ -1,8 +1,8 @@
1
- # Tested builds and open-source release readiness
1
+ # Tested builds and GitHub previews
2
2
 
3
3
  ## Build and promote a tested preview
4
4
 
5
- Repository visibility is an explicit maintainer choice. The manifest is public-release-ready, but GitHub previews do not stage or publish to npm. No npm account, package registry token or new persistent credential is required for the preview pipeline. The separate [npm staging guide](PUBLISHING.md) describes explicit bootstrap/trust setup and human approval.
5
+ Repository visibility is an explicit maintainer choice. This repository is public, but GitHub previews do not stage or publish to npm. No npm account, package registry token or new persistent credential is required for the preview pipeline. The separate [npm staging guide](PUBLISHING.md) describes the ongoing stage-only release flow and human approval. Package bootstrap is already complete.
6
6
 
7
7
  Every push and pull request runs locked installation with lifecycle scripts disabled, runtime dependency audit, lint, formatting, types, unit tests, build freshness, package allowlist and offline OpenCode integration. CI packs once, installs and tests that exact tarball, audits its newly resolved consumer dependencies, then saves `package-<full-commit>` for 14 days. The artifact contains the `.tgz`, `build-manifest.json` and `SHA256SUMS`. A PR artifact identifies its tested synthetic merge commit; prefer a push artifact when pinning a branch.
8
8
 
@@ -31,7 +31,7 @@ gh release download preview-COMMIT_PREFIX --repo strantalis/opencode-codex-secur
31
31
  shasum -a 256 -c SHA256SUMS
32
32
  # Linux: sha256sum -c SHA256SUMS
33
33
  cat build-manifest.json
34
- npm install --prefix ./consumer --ignore-scripts --no-audit --no-fund ./opencode-codex-security-0.1.1.tgz
34
+ npm install --prefix ./consumer --ignore-scripts --no-audit --no-fund ./opencode-codex-security-0.1.2.tgz
35
35
  ```
36
36
 
37
37
  The tarball version follows the package manifest; the release tag, full source commit and hash uniquely identify each build. Use the archive filename actually listed in `build-manifest.json`, which may differ from the example above. A GitHub preview does not make that version live on npm. Set your OpenCode plugin's `package` to the absolute path of `consumer/node_modules/opencode-codex-security` while preserving unrelated configuration. Select `engine: fixture` for offline testing or your explicitly configured SDK mode. Keep each preview in a separate directory; switch/reload only after active scans, approval dialogs and policy forms finish. Keep the previous directory to roll back. Nothing auto-updates the watched installation.
@@ -44,7 +44,7 @@ Develop in a separate checkout/worktree, push fixes, inspect that commit's CI, m
44
44
 
45
45
  ## npm distribution
46
46
 
47
- Public GitHub distribution under Apache 2.0 is approved. Version `0.1.0` is live on npm. The separate manual `npm-stage.yml` workflow stages later tested artifacts using maintainer-configured stage-only trust. No account, npm package or persistent trust is created by adding that workflow. Every live npm version still requires human 2FA approval. See [publishing and operating the staging flow](PUBLISHING.md).
47
+ Public GitHub distribution under Apache 2.0 is approved. Verified November 10, 2026: npm `latest` is `0.1.0`, and `0.1.1` is public under `next`. The separate manual `npm-stage.yml` workflow uses maintainer-configured trust to stage later tested artifacts; it never approves them. Bootstrap and the OIDC staging flow are established; routine releases reuse the existing trust binding. Every live npm version still requires human 2FA approval. See [publishing and operating the staging flow](PUBLISHING.md).
48
48
 
49
49
  Keep development fixes in separate worktrees and PRs; do not overwrite the watched local installation. For npm fixes, bump the patch version and lockfile together. For prerelease testing use a full prerelease version with `next`; use `latest` only deliberately for stable releases. Pin the full installed version, keep the prior consumer for rollback and reload only after active operations finish.
50
50
 
package/docs/SDK.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # Codex Security SDK integration
2
2
 
3
- Pinned SDK: `@openai/codex-security@0.9.1`; OpenCode/plugin: 2.2.6.
3
+ Pinned SDK: `@openai/codex-security@0.9.1`; plugin API package: `@opencode/plugin@2.2.6`. OpenCode 2.2.6 is the original host-test baseline, not an exact-patch requirement.
4
4
 
5
5
  Current global configuration is documented in [GLOBAL-CONFIG.md](GLOBAL-CONFIG.md). No per-project SDK overrides are loaded. Session-supplied target/focus/context is distinct from global model, reasoning and Daybreak settings. Optional per-scan Standard/Deep mode overrides only the global mode fallback; no other SDK settings are overridden by session inputs.
6
6
 
7
7
  ## Pipeline
8
8
 
9
- 1. `security_prepare_scan` or the native review action resolves the current OpenCode location (or explicit repository), canonical scope paths, focused instructions and bounded documentation files. It reads and hashes the global settings file. No SDK client, login or inference is created.
10
- 2. Preview includes target, added context, model/reasoning, Daybreak/access program and estimated cost threshold. The whole conversation is not copied.
9
+ 1. `security_prepare_scan` or the native review action resolves the current OpenCode location (or explicit repository), canonical scope paths, focused instructions and bounded documentation files. It reads and hashes the global settings file and, when explicitly configured, imports an allowlisted custom-provider selection from a Codex TOML file. `codex.useCodexConfig: true` defaults to `$CODEX_HOME/config.toml`, or `~/.codex/config.toml` when that variable is unset/empty. An explicit `codex.configFile` wins without consulting `CODEX_HOME`; no import occurs by default. Only validated selection fields, not raw TOML or secret values, enter the source digest. No SDK client, login or inference is created.
10
+ 2. Preview includes target, added context, provider/base URL, auth mode, environment-key name, model/reasoning, source selection, Daybreak/access program and estimated cost threshold. The whole conversation is not copied.
11
11
  3. `security_run_scan` checks native OpenCode permissions. Applicable saved authorization can allow it without a redundant prompt. Native UI uses an explicit review action. Missing/mismatched/reused previews fail closed.
12
- 4. Before starting, the plugin revalidates target/context and global config fingerprints. A change or expired preview requires a new review.
13
- 5. The adapter creates an SDK client with the global model, reasoning and Daybreak constructor overrides and the fixed OpenAI provider. It invokes `run(repository, options)` with scope, prompt, documentation paths, configured scan settings, fixed ChatGPT auth and disabled owner suggestions.
12
+ 4. Before starting, the plugin revalidates target/context and global config, resolved source-file location and selected-provider fingerprints. A change or expired preview requires a new review.
13
+ 5. The adapter creates an SDK client from the resolved model, reasoning, Daybreak and provider snapshot. Default routing uses OpenAI plus dedicated ChatGPT auth. An explicit custom Responses provider uses `auth: "api-key"` and its selected environment key. It invokes `run(repository, options)` with scope, prompt, documentation paths, configured scan settings and disabled owner suggestions. Policy generation and validation use the same resolved route; validation separately requires uncapped approval.
14
14
  6. Progress and sanitized lifecycle events update the panel. Findings arrive at completion. Informational findings are counted but not relabeled in the four-severity display. Local SDK artifacts remain authoritative.
15
15
 
16
16
  ## Safety and authorization boundaries
@@ -23,13 +23,25 @@ Current global configuration is documented in [GLOBAL-CONFIG.md](GLOBAL-CONFIG.m
23
23
  - Models and account entitlement are not checked while loading settings. Daybreak is a separate toggle, not a model; the toggle does not grant access.
24
24
  - Cost limits use estimates, not a hard billing cap. In-flight work can exceed the threshold.
25
25
 
26
- ## Dedicated authentication
26
+ ## Custom-provider authentication
27
27
 
28
- Real operations require an existing private, owned, nonsymlink dedicated auth file and directory. A metadata-only guard runs before constructing the real SDK client. It does not read tokens, perform login or import credentials. The injected mock clients bypass this real-auth precondition.
28
+ Provider routing is an upcoming source-version `0.1.2` feature. Published `0.1.0`/`0.1.1` packages do not expose it.
29
29
 
30
- The SDK uses `$CODEX_SECURITY_STATE_DIR/codex-home`, otherwise `$CODEX_HOME/state/plugins/codex-security/codex-home`, with `~/.codex` as the default Codex home. Keep this outside the target repository.
30
+ See [the no-sign-in setup](GLOBAL-CONFIG.md#use-an-existing-codexlitellm-provider-without-sign-in). Custom-provider readiness validates the non-secret routing and checks only the selected environment key's presence before SDK construction. It does not consult `account()`, log in, read `auth.json`, or import ambient credentials. The SDK's custom `env_key` path skips ambient-auth import; explicit API-key mode avoids stored-auth fallback. The plugin passes a snapshot, never the original SDK profile or unrelated settings.
31
31
 
32
- SDK 0.9.1 can import ambient credentials when its dedicated home is empty. The guard prevents ordinary missing-auth startup, but is not an atomic no-import policy: external deletion after the check can still race. Windows is deliberately rejected until a safe ACL verification path exists.
32
+ Custom-provider Daybreak, auth commands, inline tokens, custom headers and catalogs are deliberately unsupported. Unknown model aliases can fail SDK cost estimation; the scan/policy limit is retained rather than bypassed. Known model estimates are not verified gateway billing rates. Gateways may route onward to upstream services, and Responses compatibility does not prove the required tool support. Mock tests do not establish real gateway compatibility.
33
+
34
+ ## Default dedicated ChatGPT authentication
35
+
36
+ The default ChatGPT route requires an existing private, owned, nonsymlink dedicated auth file and directory. A metadata-only guard runs before constructing the real SDK client. It does not read tokens, perform login or import credentials. The injected mock clients bypass this real-auth precondition.
37
+
38
+ The SDK uses `$CODEX_SECURITY_STATE_DIR/codex-home`, otherwise `$CODEX_HOME/state/plugins/codex-security/codex-home`, with `~/.codex` as the default Codex home. Keep this outside the target and enclosing repositories. The login command and OpenCode process must use the same environment.
39
+
40
+ Only users choosing this default ChatGPT route should use its sign-in flow. If dedicated sign-in is absent, they can choose to complete the installed official CLI flow from the consumer directory: `./node_modules/.bin/codex-security login`. This creates a separate session through interactive sign-in, not a plugin startup action. Never ask for secrets in chat, read/copy private token files or import an ambient Codex sign-in to satisfy this guard. Do not run SDK `login status` or account helpers before dedicated credentials exist: they can import ambient credentials. These sign-in instructions are not needed for an explicitly selected custom-provider environment-key route.
41
+
42
+ The guard requires a private dedicated `auth.json`; keyring-only authentication is insufficient. Do not automatically change credential storage, managed-device policy or permissions to make it pass. Report the incompatibility and use the official supported account setup. See [agent setup](https://github.com/strantalis/opencode-codex-security/blob/main/docs/AGENT-SETUP.md) for the complete installation prompt.
43
+
44
+ SDK 0.9.1 can import ambient credentials when its dedicated home is empty. The guard prevents ordinary missing-auth startup, but is not an atomic no-import policy: external deletion after the check can still race. The ChatGPT guard deliberately rejects Windows until a safe ACL verification path exists. Custom-provider key checks are platform-neutral, but this project has not verified a complete Windows SDK operation.
33
45
 
34
46
  ## Verified behavior
35
47
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-codex-security",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -31,6 +31,7 @@
31
31
  "dependencies": {
32
32
  "@openai/codex-security": "0.9.1",
33
33
  "@opencode/plugin": "2.2.6",
34
+ "smol-toml": "1.9.1",
34
35
  "solid-js": "^1.9.0"
35
36
  },
36
37
  "devDependencies": {
@@ -9,7 +9,8 @@ import {
9
9
  } from './run-telemetry.ts';
10
10
  import { summarizeScanResult } from './result-summary.ts';
11
11
  import {
12
- requireDedicatedSecurityCredentials,
12
+ requireSdkCredentials,
13
+ ProviderCredentialsRequiredError,
13
14
  DedicatedSecurityCredentialsRequiredError,
14
15
  } from './credential-guard.ts';
15
16
  import { safeFinding } from './report-safety.ts';
@@ -22,7 +23,8 @@ import type {
22
23
  ScanResult,
23
24
  Finding as SdkFinding,
24
25
  } from '@openai/codex-security';
25
- import type { ResolvedSdkSettings } from './sdk-settings.ts';
26
+ import { sdkConfiguration, type ResolvedSdkSettings } from './sdk-settings.ts';
27
+ import { ProviderConfigurationError } from './provider-config.ts';
26
28
  import type { Finding, ScanState } from '../types.js';
27
29
 
28
30
  /** Per-run consent, never persisted. The caller must obtain this from the user. */
@@ -42,9 +44,9 @@ export type SecurityFactory = (
42
44
  config?: CodexSecurityConfig,
43
45
  ) => SecurityClient | Promise<SecurityClient>;
44
46
  const realClient: SecurityFactory = async (config) => {
45
- await requireDedicatedSecurityCredentials();
47
+ await requireSdkCredentials(config);
46
48
  const { CodexSecurity } = await import('@openai/codex-security');
47
- return new CodexSecurity(config ?? { codexOverrides: { model_provider: 'openai' } });
49
+ return new CodexSecurity(config ?? sdkConfiguration());
48
50
  };
49
51
  function initial(repository: string, revision = 0, instanceId = ''): ScanState {
50
52
  return {
@@ -121,6 +123,14 @@ export function scanBudgetFailure(
121
123
  };
122
124
  }
123
125
 
126
+ export function unsupportedPricingMessage(error: unknown): string | undefined {
127
+ if (
128
+ error instanceof Error &&
129
+ error.message.startsWith('A scan cost limit is not available for the configured model:')
130
+ )
131
+ return 'The SDK cannot estimate costs for this model or gateway alias, so the operation stopped without removing its cost limit. Use a gateway model with SDK-supported pricing, or ask the gateway administrator about a compatible route. Estimates are not gateway billing totals or hard caps.';
132
+ }
133
+
124
134
  /** Local coordinator; SDK inference is network-backed, not an offline scanner. */
125
135
  export class CodexScanService {
126
136
  readonly root: string;
@@ -198,18 +208,15 @@ export class CodexScanService {
198
208
  let terminalPhase = 'Codex Security scan failed';
199
209
  try {
200
210
  signal.throwIfAborted();
201
- client = await this.#factory({
202
- codexOverrides: {
203
- model_provider: 'openai',
204
- ...(approval.settings
205
- ? {
206
- model: approval.settings.codex.model,
207
- model_reasoning_effort: approval.settings.codex.reasoningEffort,
208
- daybreak: approval.settings.codex.daybreak,
209
- }
210
- : {}),
211
- },
212
- });
211
+ if (
212
+ approval.settings?.codex.provider &&
213
+ approval.auth &&
214
+ approval.auth !== approval.settings.codex.auth
215
+ )
216
+ throw new ProviderConfigurationError(
217
+ 'Approved authentication does not match the selected provider.',
218
+ );
219
+ client = await this.#factory(sdkConfiguration(approval.settings));
213
220
  signal.throwIfAborted();
214
221
  const telemetry = (this.#state.telemetry ??= emptyTelemetry());
215
222
  const options: ScanOptions = {
@@ -218,7 +225,7 @@ export class CodexScanService {
218
225
  knowledgeBasePaths: [...(approval.contextFiles ?? [])],
219
226
  ...approval.settings?.scan,
220
227
  mode: approval.settings?.scan.mode ?? 'standard',
221
- auth: approval.auth ?? 'chatgpt',
228
+ auth: approval.settings?.codex.auth ?? approval.auth ?? 'chatgpt',
222
229
  maxCostUsd: approval.maxCostUsd,
223
230
  ownerSuggestions: false,
224
231
  signal,
@@ -380,9 +387,12 @@ export class CodexScanService {
380
387
  this.#state.error = signal.aborted
381
388
  ? null
382
389
  : (budget?.message ??
383
- (error instanceof DedicatedSecurityCredentialsRequiredError
390
+ (error instanceof DedicatedSecurityCredentialsRequiredError ||
391
+ error instanceof ProviderCredentialsRequiredError ||
392
+ error instanceof ProviderConfigurationError
384
393
  ? error.message
385
- : 'SDK scan failed. Check authentication, runtime prerequisites, and local SDK diagnostics.'));
394
+ : (unsupportedPricingMessage(error) ??
395
+ 'SDK scan failed. Check authentication, runtime prerequisites, and local SDK diagnostics.')));
386
396
  terminalMessage = signal.aborted
387
397
  ? 'Cancellation requested; SDK run stopped. Partial results are not a completed scan.'
388
398
  : this.#state.error!;
@@ -81,3 +81,50 @@ export async function requireDedicatedSecurityCredentials(
81
81
  throw new DedicatedSecurityCredentialsRequiredError();
82
82
  }
83
83
  }
84
+
85
+ /** A fixed message, safe to expose without leaking credential values or SDK diagnostics. */
86
+ export class ProviderCredentialsRequiredError extends Error {
87
+ constructor() {
88
+ super(
89
+ 'The selected provider environment key is missing or blank. Make the configured envKey available to the OpenCode process before retrying. No sign-in or credential import was attempted.',
90
+ );
91
+ this.name = 'ProviderCredentialsRequiredError';
92
+ }
93
+ }
94
+
95
+ /** The only API-key readiness check: validate routing, then check the named key's presence. */
96
+ export async function requireSdkCredentials(
97
+ config: import('@openai/codex-security').CodexSecurityConfig | undefined,
98
+ environment: Environment = process.env,
99
+ ): Promise<void> {
100
+ const routing = config?.codexOverrides;
101
+ const id = routing?.model_provider;
102
+ if (id === undefined || id === 'openai') return requireDedicatedSecurityCredentials(environment);
103
+ const { validateProvider, record, ProviderConfigurationError } =
104
+ await import('./provider-config.ts');
105
+ const providers = routing?.model_providers;
106
+ const native = typeof id === 'string' && record(providers) ? providers[id] : undefined;
107
+ if (
108
+ !record(native) ||
109
+ native.requires_openai_auth !== false ||
110
+ Object.keys(native).some(
111
+ (key) => !['name', 'base_url', 'env_key', 'wire_api', 'requires_openai_auth'].includes(key),
112
+ )
113
+ )
114
+ throw new ProviderConfigurationError(
115
+ 'Invalid API-key provider routing. No authentication was initialized.',
116
+ );
117
+ const provider = validateProvider({
118
+ id,
119
+ baseUrl: native.base_url,
120
+ envKey: native.env_key,
121
+ wireApi: native.wire_api,
122
+ });
123
+ // Exact environment-variable selection on POSIX, case-insensitive on Windows.
124
+ // Never enumerate or copy environment values, return a key, or inspect auth.json.
125
+ const name =
126
+ process.platform === 'win32'
127
+ ? Object.keys(environment).find((key) => key.toUpperCase() === provider.envKey.toUpperCase())
128
+ : provider.envKey;
129
+ if (!name || !environment[name]?.trim()) throw new ProviderCredentialsRequiredError();
130
+ }
@@ -271,12 +271,43 @@ export class PlannedScanService {
271
271
  void this.#save(entry.state).catch(() => {});
272
272
  }
273
273
  });
274
+ try {
275
+ await validatePlan(plan);
276
+ } catch {
277
+ entry.state = {
278
+ ...entry.state,
279
+ status: this.#disposed || entry.cancelledBeforeStart ? 'cancelled' : 'error',
280
+ phase:
281
+ this.#disposed || entry.cancelledBeforeStart
282
+ ? 'Cancelled before execution'
283
+ : 'Preview invalid; prepare again',
284
+ error:
285
+ this.#disposed || entry.cancelledBeforeStart
286
+ ? null
287
+ : 'Target, context, or provider settings changed after approval. Prepare and review a new scan.',
288
+ finishedAt: new Date().toISOString(),
289
+ };
290
+ await this.#save(entry.state).catch(() => {});
291
+ this.#emit(entry.state);
292
+ return structuredClone(entry.state);
293
+ }
294
+ if (this.#disposed || entry.cancelledBeforeStart) {
295
+ entry.state = {
296
+ ...entry.state,
297
+ status: 'cancelled',
298
+ phase: 'Cancelled before execution',
299
+ finishedAt: new Date().toISOString(),
300
+ };
301
+ await this.#save(entry.state).catch(() => {});
302
+ this.#emit(entry.state);
303
+ return structuredClone(entry.state);
304
+ }
274
305
  service.start({
275
306
  approved: true,
276
307
  repository: plan.repository,
277
308
  allowNetworkModelInference: true,
278
309
  maxCostUsd: plan.settings.scan.maxCostUsd,
279
- auth: 'chatgpt',
310
+ auth: plan.settings.codex.auth,
280
311
  settings: plan.settings,
281
312
  scopePaths: [...plan.scopePaths],
282
313
  scanPrompt: plan.scanPrompt,