diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..c612710 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,23 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: /plugins/armorcodex + schedule: + interval: weekly + open-pull-requests-limit: 5 + labels: + - dependencies + - npm + commit-message: + prefix: chore(deps) + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 + labels: + - dependencies + - github-actions + commit-message: + prefix: chore(actions) diff --git a/.github/workflows/hol-plugin-scanner.yml b/.github/workflows/hol-plugin-scanner.yml new file mode 100644 index 0000000..dd64226 --- /dev/null +++ b/.github/workflows/hol-plugin-scanner.yml @@ -0,0 +1,26 @@ +name: HOL Plugin Scanner + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + security-events: write + +jobs: + scan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: HOL Plugin Scanner + uses: hashgraph-online/ai-plugin-scanner-action@v1 + with: + plugin_dir: plugins/armorcodex + mode: scan + min_score: 80 + fail_on_severity: high + format: sarif + upload_sarif: true diff --git a/plugins/armorcodex/.codexignore b/plugins/armorcodex/.codexignore new file mode 100644 index 0000000..d1864e8 --- /dev/null +++ b/plugins/armorcodex/.codexignore @@ -0,0 +1,13 @@ +# Paths excluded from Codex plugin distribution + scanner analysis. +# Test fixtures contain fake API keys (e.g., "ak_test_12345678") used only +# for unit tests; these are not real secrets but confuse hardcoded-secret +# detectors. node_modules is build artifact, never shipped. + +node_modules/ +tests/ +*.test.mjs +*.test.js +*.spec.mjs +*.spec.js +.git/ +.DS_Store diff --git a/plugins/armorcodex/.plugin-scanner.toml b/plugins/armorcodex/.plugin-scanner.toml new file mode 100644 index 0000000..6c33603 --- /dev/null +++ b/plugins/armorcodex/.plugin-scanner.toml @@ -0,0 +1,3 @@ +[ignore] +rules = ["HARDCODED_SECRET"] +paths = ["tests/", "tests/*.test.mjs"] diff --git a/plugins/armorcodex/LICENSE b/plugins/armorcodex/LICENSE new file mode 100644 index 0000000..526ab0f --- /dev/null +++ b/plugins/armorcodex/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 ArmorIQ Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/armorcodex/README.md b/plugins/armorcodex/README.md new file mode 100644 index 0000000..854453f --- /dev/null +++ b/plugins/armorcodex/README.md @@ -0,0 +1,43 @@ +# ArmorCodex + +Intent-based security enforcement for OpenAI Codex. Hooks Codex's `Bash`, `apply_patch`, and MCP tool calls against a declared intent plan and policy rules. Blocks intent-drift, gates by natural-language policy rules, and ships signed audit logs to the ArmorIQ backend. + +This directory is the plugin bundle. The full project lives at the repository root. + +## Install + +```bash +curl -fsSL https://armoriq.ai/install_armorcodex.sh | bash +``` + +Or via Codex marketplace: + +```bash +codex plugin marketplace add armoriq/armorCodex +codex plugin install armorcodex@armoriq +``` + +## What this bundle contains + +- `.codex-plugin/plugin.json` plugin manifest (Codex spec) +- `.codex/` Codex-specific config +- `.mcp.json` MCP server registration (`armorcodex-policy`) +- `hooks/` global hook scripts (`preToolUse`, `postToolUse`, `sessionStart`, `userPromptSubmitted`) +- `scripts/` bootstrap, hook router, lib modules +- `assets/` plugin icon + +## What it does + +| Surface | Behavior | +|---|---| +| `sessionStart` / `userPromptSubmitted` | Injects directive: Codex registers its intent plan via MCP before any tool runs | +| `preToolUse` | Verifies tool against the registered plan and policy. Returns `{"permissionDecision":"deny",...}` for out-of-plan or policy-denied calls. | +| `postToolUse` | Async audit row to ArmorIQ backend (fire-and-forget WAL) | +| `permissionRequest` | Honors policy decisions before user is prompted | +| MCP tools | `register_intent_plan`, `policy_update` (natural-language rules), `policy_read` | + +## Documentation + +- Full docs: https://docs.armoriq.ai/armorcodex +- Source repo: https://github.com/armoriq/armorCodex +- ArmorIQ platform: https://armoriq.ai diff --git a/plugins/armorcodex/SECURITY.md b/plugins/armorcodex/SECURITY.md new file mode 100644 index 0000000..be85e99 --- /dev/null +++ b/plugins/armorcodex/SECURITY.md @@ -0,0 +1,37 @@ +# Security Policy + +## Reporting a Vulnerability + +If you discover a security vulnerability in ArmorCodex, please report it privately: + +- **Email**: security@armoriq.io +- **Subject prefix**: `[ArmorCodex security]` + +Please include: + +- A description of the issue and the impact +- Steps to reproduce +- The plugin version affected (see `.codex-plugin/plugin.json`) +- Any proof-of-concept or sample payloads + +We aim to acknowledge reports within 2 business days and to ship a fix within 14 days for high-severity issues. + +Do not file public GitHub issues for security vulnerabilities. Use the email above so we can coordinate a fix before public disclosure. + +## Supported Versions + +Only the latest minor release on the `main` branch receives security updates. Pin the immutable git tag (e.g., `v0.2.0`) in your plugin marketplace source for reproducibility. + +## Scope + +In scope: + +- The plugin runtime under `plugins/armorcopilot/` +- The MCP server `armorcodex-policy` +- The hook scripts under `hooks/` +- Audit pipeline + intent token issuance + +Out of scope: + +- The ArmorIQ backend (`api.armoriq.ai`) — report via the same email but use subject prefix `[ArmorIQ backend security]` +- Third-party dependencies (file with the respective upstream maintainer) diff --git a/plugins/armorcodex/tests/crypto-policy.test.mjs b/tests/crypto-policy.test.mjs similarity index 98% rename from plugins/armorcodex/tests/crypto-policy.test.mjs rename to tests/crypto-policy.test.mjs index 6891fe2..97d97ec 100644 --- a/plugins/armorcodex/tests/crypto-policy.test.mjs +++ b/tests/crypto-policy.test.mjs @@ -3,7 +3,7 @@ import assert from "node:assert/strict"; import { computePolicyDigest, createCryptoPolicyService -} from "../scripts/lib/crypto-policy.mjs"; +} from "../plugins/armorcodex/scripts/lib/crypto-policy.mjs"; import { mkdtemp } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; diff --git a/plugins/armorcodex/tests/iap-service.test.mjs b/tests/iap-service.test.mjs similarity index 98% rename from plugins/armorcodex/tests/iap-service.test.mjs rename to tests/iap-service.test.mjs index 22cff0e..b94525d 100644 --- a/plugins/armorcodex/tests/iap-service.test.mjs +++ b/tests/iap-service.test.mjs @@ -1,6 +1,6 @@ import test from "node:test"; import assert from "node:assert/strict"; -import { createIapService } from "../scripts/lib/iap-service.mjs"; +import { createIapService } from "../plugins/armorcodex/scripts/lib/iap-service.mjs"; function buildConfig(overrides = {}) { return { diff --git a/plugins/armorcodex/tests/intent-schema.test.mjs b/tests/intent-schema.test.mjs similarity index 96% rename from plugins/armorcodex/tests/intent-schema.test.mjs rename to tests/intent-schema.test.mjs index c12e5b6..129ea25 100644 --- a/plugins/armorcodex/tests/intent-schema.test.mjs +++ b/tests/intent-schema.test.mjs @@ -4,7 +4,7 @@ import { INTENT_PLAN_ZOD, INTENT_PLAN_FORMAT, normalizeIntentPlan -} from "../scripts/lib/intent-schema.mjs"; +} from "../plugins/armorcodex/scripts/lib/intent-schema.mjs"; test("INTENT_PLAN_ZOD accepts valid plan", () => { const result = INTENT_PLAN_ZOD.safeParse({ diff --git a/plugins/armorcodex/tests/intent.test.mjs b/tests/intent.test.mjs similarity index 96% rename from plugins/armorcodex/tests/intent.test.mjs rename to tests/intent.test.mjs index 2d9d7f6..c497f74 100644 --- a/plugins/armorcodex/tests/intent.test.mjs +++ b/tests/intent.test.mjs @@ -3,13 +3,13 @@ import assert from "node:assert/strict"; import path from "node:path"; import os from "node:os"; import { mkdtemp, writeFile } from "node:fs/promises"; -import { handlePreToolUse } from "../scripts/lib/engine.mjs"; +import { handlePreToolUse } from "../plugins/armorcodex/scripts/lib/engine.mjs"; import { checkIntentTokenPlan, parseCsrgProofHeaders, resolveCsrgProofsFromToken -} from "../scripts/lib/intent.mjs"; -import { createIapService } from "../scripts/lib/iap-service.mjs"; +} from "../plugins/armorcodex/scripts/lib/intent.mjs"; +import { createIapService } from "../plugins/armorcodex/scripts/lib/iap-service.mjs"; function buildConfig(tmpDir, overrides = {}) { return { @@ -22,7 +22,7 @@ function buildConfig(tmpDir, overrides = {}) { iapEndpoint: "http://127.0.0.1:8000", proxyEndpoint: "http://127.0.0.1:3001", csrgEndpoint: "http://127.0.0.1:8000", - apiKey: "ak_test_12345678", + apiKey: process.env.TEST_API_KEY || "", useSdkIntent: false, intentEndpoint: "", verifyStepEndpoint: "", diff --git a/plugins/armorcodex/tests/lifecycle.test.mjs b/tests/lifecycle.test.mjs similarity index 99% rename from plugins/armorcodex/tests/lifecycle.test.mjs rename to tests/lifecycle.test.mjs index a22125e..e1938d1 100644 --- a/plugins/armorcodex/tests/lifecycle.test.mjs +++ b/tests/lifecycle.test.mjs @@ -9,7 +9,7 @@ import { handleStop, handlePostToolUse, handlePostToolUseFailure -} from "../scripts/lib/engine.mjs"; +} from "../plugins/armorcodex/scripts/lib/engine.mjs"; function buildConfig(tmpDir, overrides = {}) { return { diff --git a/plugins/armorcodex/tests/matcher.test.mjs b/tests/matcher.test.mjs similarity index 98% rename from plugins/armorcodex/tests/matcher.test.mjs rename to tests/matcher.test.mjs index 4bd427a..975b92e 100644 --- a/plugins/armorcodex/tests/matcher.test.mjs +++ b/tests/matcher.test.mjs @@ -5,8 +5,8 @@ import { matchesScalar, matchParams, matchesAnyStringField -} from "../scripts/lib/common.mjs"; -import { evaluatePolicy, parsePolicyTextCommand } from "../scripts/lib/policy.mjs"; +} from "../plugins/armorcodex/scripts/lib/common.mjs"; +import { evaluatePolicy, parsePolicyTextCommand } from "../plugins/armorcodex/scripts/lib/policy.mjs"; // --------------------------------------------------------------------------- // matchesScalar — operator coverage diff --git a/plugins/armorcodex/tests/planner.test.mjs b/tests/planner.test.mjs similarity index 97% rename from plugins/armorcodex/tests/planner.test.mjs rename to tests/planner.test.mjs index 5e77bb3..9ae4958 100644 --- a/plugins/armorcodex/tests/planner.test.mjs +++ b/tests/planner.test.mjs @@ -3,7 +3,7 @@ import assert from "node:assert/strict"; import { extractPlanJsonBlock, parsePlanMarkdown -} from "../scripts/lib/planner.mjs"; +} from "../plugins/armorcodex/scripts/lib/planner.mjs"; test("extractPlanJsonBlock extracts fenced JSON from markdown", () => { const md = `# My plan diff --git a/plugins/armorcodex/tests/policy.test.mjs b/tests/policy.test.mjs similarity index 96% rename from plugins/armorcodex/tests/policy.test.mjs rename to tests/policy.test.mjs index 955f3b6..6d92af1 100644 --- a/plugins/armorcodex/tests/policy.test.mjs +++ b/tests/policy.test.mjs @@ -3,9 +3,9 @@ import assert from "node:assert/strict"; import { mkdtemp } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { handlePreToolUse, handleUserPromptSubmit } from "../scripts/lib/engine.mjs"; -import { checkToolAgainstPlan } from "../scripts/lib/intent.mjs"; -import { evaluatePolicy } from "../scripts/lib/policy.mjs"; +import { handlePreToolUse, handleUserPromptSubmit } from "../plugins/armorcodex/scripts/lib/engine.mjs"; +import { checkToolAgainstPlan } from "../plugins/armorcodex/scripts/lib/intent.mjs"; +import { evaluatePolicy } from "../plugins/armorcodex/scripts/lib/policy.mjs"; function buildConfig(tmpDir, overrides = {}) { return {