Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ npx @openai/codex-security scan . --provider openrouter --model anthropic/claude

export FIREWORKS_API_KEY="<your-fireworks-api-key>"
npx @openai/codex-security scan . --provider fireworks --model accounts/fireworks/models/qwen3-235b-a22b

export HF_TOKEN="<your-huggingface-token>"
npx @openai/codex-security scan . --provider huggingface --model openai/gpt-oss-120b
```

Local sign-in honors Codex's configured credential backend, including a system
Expand Down
10 changes: 10 additions & 0 deletions sdk/typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ npx @openai/codex-security scan . --provider openrouter --model anthropic/claude

export FIREWORKS_API_KEY="<your-fireworks-api-key>"
npx @openai/codex-security scan . --provider fireworks --model accounts/fireworks/models/qwen3-235b-a22b

export HF_TOKEN="<your-huggingface-token>"
npx @openai/codex-security scan . --provider huggingface --model openai/gpt-oss-120b
```

On Windows, set the API key in PowerShell:
Expand Down Expand Up @@ -418,6 +421,13 @@ Use `--provider openrouter` to send inference through OpenRouter. Set
Use `--provider fireworks` to send inference through Fireworks AI. Set
`FIREWORKS_API_KEY` and specify a supported model with `--model`.

Use `--provider huggingface` to send inference through
[Hugging Face Inference Providers](https://huggingface.co/docs/inference-providers/en/index).
Set `HF_TOKEN` (fine-grained token with Inference Providers permission) and
specify a supported model with `--model`. Append a provider or policy suffix
when needed (for example `openai/gpt-oss-120b:groq` or
`openai/gpt-oss-120b:cheapest`).

Scan progress identifies the requested paths and reports actual ranking,
file-review, validation, and attack-path phases as they become available.
Completion summarizes findings, severity, coverage, elapsed time, available
Expand Down
16 changes: 5 additions & 11 deletions sdk/typescript/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ import {
type AccountStatus,
} from "./auth.js";
import {
EXTERNAL_CODEX_PROVIDER_ENV_KEYS,
EXTERNAL_CODEX_PROVIDERS,
isExternalModelProvider,
mergedCodexConfig,
scanModelConfiguration,
type CodexSecurityConfig,
type ExternalProviderEnvKey,
type JsonObject,
writeCodexConfig,
} from "./config.js";
Expand Down Expand Up @@ -173,11 +175,7 @@ export type ScanAuthMode = "auto" | "chatgpt" | "api-key";
export type ScanAuthentication =
| {
method: "api_key";
source:
| "OPENAI_API_KEY"
| "CODEX_API_KEY"
| "OPENROUTER_API_KEY"
| "FIREWORKS_API_KEY";
source: "OPENAI_API_KEY" | "CODEX_API_KEY" | ExternalProviderEnvKey;
verified: false;
}
| {
Expand Down Expand Up @@ -1903,7 +1901,7 @@ function selectedScanEnvironment(
const key = name.toUpperCase();
if (key === "OPENAI_API_KEY" || key === "CODEX_API_KEY") return false;
if (selectedProviderKey === null) return true;
if (key === "OPENROUTER_API_KEY" || key === "FIREWORKS_API_KEY") {
if (EXTERNAL_CODEX_PROVIDER_ENV_KEYS.has(key)) {
return key === selectedProviderKey;
}
return true;
Expand Down Expand Up @@ -1936,11 +1934,7 @@ function environmentApiKeyEntry(
environment: ProcessEnvironment,
modelProvider?: unknown,
): {
source:
| "OPENAI_API_KEY"
| "CODEX_API_KEY"
| "OPENROUTER_API_KEY"
| "FIREWORKS_API_KEY";
source: "OPENAI_API_KEY" | "CODEX_API_KEY" | ExternalProviderEnvKey;
value: string;
} | null {
const keys = isExternalModelProvider(modelProvider)
Expand Down
2 changes: 1 addition & 1 deletion sdk/typescript/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ const VALUE_OPTIONS = new Set([
"--reason",
]);
const PROVIDER_OPTION = z
.enum(["openai", "openrouter", "fireworks"])
.enum(["openai", "openrouter", "fireworks", "huggingface"])
.default("openai")
.describe("Inference provider for scans.");

Expand Down
15 changes: 15 additions & 0 deletions sdk/typescript/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,28 @@ export const FIREWORKS_CODEX_PROVIDER = {
wire_api: "responses",
} as const satisfies JsonObject;

export const HUGGINGFACE_CODEX_PROVIDER = {
name: "Hugging Face",
base_url: "https://router.huggingface.co/v1",
env_key: "HF_TOKEN",
wire_api: "responses",
} as const satisfies JsonObject;

export const EXTERNAL_CODEX_PROVIDERS = {
openrouter: OPENROUTER_CODEX_PROVIDER,
fireworks: FIREWORKS_CODEX_PROVIDER,
huggingface: HUGGINGFACE_CODEX_PROVIDER,
} as const;

export type ExternalModelProvider = keyof typeof EXTERNAL_CODEX_PROVIDERS;

export type ExternalProviderEnvKey =
(typeof EXTERNAL_CODEX_PROVIDERS)[ExternalModelProvider]["env_key"];

export const EXTERNAL_CODEX_PROVIDER_ENV_KEYS = new Set<string>(
Object.values(EXTERNAL_CODEX_PROVIDERS).map((provider) => provider.env_key),
);

export function isExternalModelProvider(
provider: unknown,
): provider is ExternalModelProvider {
Expand Down
20 changes: 12 additions & 8 deletions sdk/typescript/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ import {
PluginBootstrapError,
PluginPythonUnavailableError,
} from "./errors.js";
import type { JsonObject } from "./config.js";
import {
EXTERNAL_CODEX_PROVIDER_ENV_KEYS,
type JsonObject,
} from "./config.js";
import { resolveTrustedExecutable } from "./trusted-executable.js";

const execFile = promisify(execFileCallback);
Expand Down Expand Up @@ -606,13 +609,14 @@ export async function runWorkbench(
],
{
env: Object.fromEntries(
Object.entries(options.environment).filter(
([name]) =>
name.toUpperCase() !== "OPENAI_API_KEY" &&
name.toUpperCase() !== "CODEX_API_KEY" &&
name.toUpperCase() !== "OPENROUTER_API_KEY" &&
name.toUpperCase() !== "FIREWORKS_API_KEY",
),
Object.entries(options.environment).filter(([name]) => {
const key = name.toUpperCase();
return (
key !== "OPENAI_API_KEY" &&
key !== "CODEX_API_KEY" &&
!EXTERNAL_CODEX_PROVIDER_ENV_KEYS.has(key)
);
}),
),
encoding: "utf8",
maxBuffer: 4 * 1024 * 1024,
Expand Down
8 changes: 8 additions & 0 deletions sdk/typescript/tests-ts/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
} from "../src/api.js";
import {
FIREWORKS_CODEX_PROVIDER,
HUGGINGFACE_CODEX_PROVIDER,
OPENROUTER_CODEX_PROVIDER,
writeCodexConfig,
type JsonObject,
Expand Down Expand Up @@ -77,6 +78,13 @@ const EXTERNAL_PROVIDER_CASES = [
"accounts/fireworks/models/qwen3-235b-a22b",
FIREWORKS_CODEX_PROVIDER,
],
[
"Hugging Face",
"huggingface",
"HF_TOKEN",
"openai/gpt-oss-120b",
HUGGINGFACE_CODEX_PROVIDER,
],
] as const;
const TestClientBase = CodexSecurity as unknown as new (
config: Record<string, unknown>,
Expand Down
36 changes: 32 additions & 4 deletions sdk/typescript/tests-ts/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { main, parseCodexOverrides, Progress } from "../src/cli.js";
import {
DEFAULT_CODEX_CONFIG,
FIREWORKS_CODEX_PROVIDER,
HUGGINGFACE_CODEX_PROVIDER,
OPENROUTER_CODEX_PROVIDER,
scanModelConfiguration,
} from "../src/config.js";
Expand Down Expand Up @@ -121,7 +122,9 @@ describe("CLI", () => {
model: { type: "string" },
verbose: { type: "boolean" },
effort: { enum: ["minimal", "low", "medium", "high", "xhigh"] },
provider: { enum: ["openai", "openrouter", "fireworks"] },
provider: {
enum: ["openai", "openrouter", "fireworks", "huggingface"],
},
failOnSeverity: { enum: ["critical", "high", "medium", "low"] },
},
},
Expand Down Expand Up @@ -759,6 +762,12 @@ describe("CLI", () => {
"accounts/fireworks/models/qwen3-235b-a22b",
FIREWORKS_CODEX_PROVIDER,
],
[
"Hugging Face",
"huggingface",
"openai/gpt-oss-120b",
HUGGINGFACE_CODEX_PROVIDER,
],
] as const)(
"routes bulk scans through %s",
async (_name, provider, model, providerConfig) => {
Expand Down Expand Up @@ -1683,7 +1692,9 @@ describe("CLI", () => {
expect(help.text()).toContain("--stop-after-no-new <number>");
expect(help.text()).toContain("--max-discovery-runs <number>");
expect(help.text()).toContain("--model <string>");
expect(help.text()).toContain("--provider <openai|openrouter|fireworks>");
expect(help.text()).toContain(
"--provider <openai|openrouter|fireworks|huggingface>",
);
expect(help.text()).toContain(
`OpenAI model to use (default: ${DEFAULT_SCAN_MODEL_CONFIGURATION.model}).`,
);
Expand Down Expand Up @@ -1749,7 +1760,9 @@ describe("CLI", () => {
);
expect(help.text()).not.toContain("--outputDir");
expect(help.text()).not.toContain("--maxAttempts");
expect(help.text()).toContain("--provider <openai|openrouter|fireworks>");
expect(help.text()).toContain(
"--provider <openai|openrouter|fireworks|huggingface>",
);
expect(stderr.text()).toBe("");
});

Expand Down Expand Up @@ -1810,6 +1823,13 @@ describe("CLI", () => {
"accounts/fireworks/models/llama-v3p3-70b-instruct",
FIREWORKS_CODEX_PROVIDER,
],
[
"Hugging Face",
"huggingface",
"openai/gpt-oss-120b",
"moonshotai/Kimi-K2-Instruct-0905:groq",
HUGGINGFACE_CODEX_PROVIDER,
],
] as const)(
"routes scans through %s",
async (_name, provider, selectedModel, codexModel, providerConfig) => {
Expand Down Expand Up @@ -1953,7 +1973,11 @@ describe("CLI", () => {
"high",
),
).toThrow("--effort conflicts with --codex model_reasoning_effort");
for (const provider of ["openrouter", "fireworks"] as const) {
for (const provider of [
"openrouter",
"fireworks",
"huggingface",
] as const) {
expect(() =>
parseCodexOverrides([], undefined, undefined, provider),
).toThrow(`--model is required when using --provider ${provider}`);
Expand Down Expand Up @@ -2047,6 +2071,10 @@ describe("CLI", () => {
["scan", ".", "--provider", "fireworks"],
"--model is required when using --provider fireworks",
],
[
["scan", ".", "--provider", "huggingface"],
"--model is required when using --provider huggingface",
],
[
["scan", ".", "--effort", "ultra"],
"--effort must be minimal, low, medium, high, or xhigh",
Expand Down
2 changes: 2 additions & 0 deletions sdk/typescript/tests-ts/runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1841,6 +1841,7 @@ describe("runtime directories and plugin Python boundary", () => {
"assert os.environ.get('CODEX_API_KEY') is None",
"assert os.environ.get('OPENROUTER_API_KEY') is None",
"assert os.environ.get('FIREWORKS_API_KEY') is None",
"assert os.environ.get('HF_TOKEN') is None",
"print(json.dumps({'ok': True}))",
].join("\n"),
);
Expand All @@ -1856,6 +1857,7 @@ describe("runtime directories and plugin Python boundary", () => {
CODEX_API_KEY: "also-must-not-reach-python",
OPENROUTER_API_KEY: "openrouter-must-not-reach-python",
FIREWORKS_API_KEY: "fireworks-must-not-reach-python",
HF_TOKEN: "huggingface-must-not-reach-python",
},
},
["test-command"],
Expand Down