diff --git a/SESSION_CONTEXT.md b/SESSION_CONTEXT.md new file mode 100644 index 0000000..ba1c542 --- /dev/null +++ b/SESSION_CONTEXT.md @@ -0,0 +1,312 @@ +# ArmorCopilot — Session Context (start here in new sessions) + +> **For a fresh Claude session:** read this file top-to-bottom before doing anything else. It's the single source of truth for what's been decided, what's been built, and what's next on ArmorCopilot. If anything below conflicts with what you see in the codebase, trust the codebase and update this file. + +--- + +## 1. Project context + +ArmorCopilot is the third product in the ArmorIQ enforcement lineup: + +| Product | Harness | Repo | Status | +|---|---|---|---| +| **ArmorClaude** | Anthropic Claude Code | `/Users/hariharasudhan/Armoriq/armorClaude` | Live in marketplace | +| **ArmorCodex** | OpenAI Codex | `/Users/hariharasudhan/Armoriq/armorCodex` | Live + listed on `hashgraph-online/awesome-codex-plugins` | +| **ArmorCopilot** (NEW) | Microsoft Copilot Studio (later: GitHub Copilot CLI) | `/Users/hariharasudhan/Armoriq/armorCopilot` | PoC stub bootstrapped 2026-05-21 | + +All three share the same conceptual model: intercept tool calls before they execute → match against a registered intent plan → consult ArmorIQ backend policy engine → block or allow → audit. + +### Why now + +Ketan (12:57 AM, 2026-05-21 in Slack): *"@Hari can you pick up ArmorCopilot Microsoft one"* + +After clarifying, Ketan confirmed (5:58 PM, 2026-05-21): **Microsoft Copilot**, not GitHub Copilot. + +--- + +## 2. Interception surface map (what we CAN and CAN'T do) + +Research completed 2026-05-21. Snapshot of the extension surface across every Copilot product: + +### Microsoft side + +| Surface | Real interception? | Notes | +|---|---|---| +| **Microsoft Copilot Studio** | **YES** | `/analyze-tool-execution` external security webhook. Sub-1s inline block/allow. Same surface Check Point + Zenity already integrate. **THIS IS OUR TARGET.** | +| M365 Copilot (Teams/Outlook/Word) | NO | Only post-hoc audit via Purview + Office Management Activity API. No pre-execution hook. Audit-only SKU possible if a customer asks. | +| M365 declarative agents | NO | Manifest 1.6 has no pre-execution guard property. Only inherits Copilot Studio hooks if hosted there. | + +### GitHub side + +| Surface | Real interception? | Notes | +|---|---|---| +| **GitHub Copilot CLI** (`gh copilot`) | **YES** | Mature `preToolUse` / `postToolUse` hooks. ~85% code reuse from ArmorCodex. Deferred to Phase B (after MS MVP). | +| VS Code Copilot agent mode | NO (yet) | Proposed permission API (microsoft/vscode#302362), not GA. | +| JetBrains Copilot agent mode | NO (only MCP) | MCP server registration, no hooks. | +| GitHub Copilot Chat (web) | NO | Cloud-executed, no extension surface. | +| GitHub App Extensions | NO (deprecated) | Sunset 2025-11-10, replaced by MCP. | + +### Out of scope (defer) + +- M365 Copilot audit-only SKU +- VS Code agent mode (wait for permission API) +- JetBrains agent mode +- AppSource / Microsoft Partner Network listing (do after first customer) + +--- + +## 3. Decisions made (2026-05-21) + +| # | Decision | Rationale | +|---|---|---| +| 1 | Target Microsoft Copilot Studio for ArmorCopilot-MS | Only MS surface with real pre-execution interception | +| 2 | Skip M365 Copilot for now | No interception, only audit. Revisit if customer asks | +| 3 | Sequence: MS first, GH after MVP (not parallel) | Ketan specifically asked for Microsoft. GH is cheap (1 week) but parallelizing now splits attention | +| 4 | Host MS webhook in `conmap-auto` (not standalone service) | Reuses `IapPolicyDecisionService` + audit pipeline + auth. Existing endpoints already meet <1s budget | +| 5 | Multi-tenant via path param `/copilot-studio/analyze-tool-execution/:tenantId` | Each customer gets unique URL + HMAC secret | +| 6 | Defer shared-core library extraction (Phase C) | Don't refactor until ArmorCopilot ships + stabilizes | +| 7 | New monorepo `armoriq/armorCopilot` | Houses both `-ms` (server-side) and `-gh` (client-side plugin) packages | + +--- + +## 4. What's done today (2026-05-21) + +### Phase 0 — bootstrap + +- **Repo:** github.com/armoriq/armorCopilot (private, cloned to `/Users/hariharasudhan/Armoriq/armorCopilot`) +- **Tracking issues:** + - [armoriq/armorCopilot#1](https://github.com/armoriq/armorCopilot/issues/1) — parent + - [armoriq/conmap-auto#247](https://github.com/armoriq/conmap-auto/issues/247) — backend `/copilot-studio/*` endpoints + - [armoriq/armorCodex#18](https://github.com/armoriq/armorCodex/issues/18) — shared core library extraction (deferred) +- **Plan doc:** [/Users/hariharasudhan/Armoriq/ARMORCOPILOT_PLAN.md](/Users/hariharasudhan/Armoriq/ARMORCOPILOT_PLAN.md) +- **Brief doc for Hui (CEO):** [/Users/hariharasudhan/Armoriq/MEETING_BRIEF_HUI_2026-05-21.md](/Users/hariharasudhan/Armoriq/MEETING_BRIEF_HUI_2026-05-21.md) + +### Phase A.8 — PoC + architectural pivot — DONE 2026-05-22 + +Originally PoC'd inside `conmap-auto` (4 commits on `feat/copilot-studio-webhook-poc`), then reverted after Hari flagged that calling the backend directly didn't match the ArmorClaude/ArmorCodex pattern. Pivoted to the right shape: standalone service in this repo + SDK integration helper. + +#### Final architecture (matches ArmorClaude/ArmorCodex) + +``` +Microsoft Copilot Studio + → HTTPS POST /analyze-tool-execution/:tenantId +@armoriq/armorcopilot-ms (this repo, Cloud Run deploy) + - verifyCopilotStudioSignature() from @armoriq/sdk ← HMAC via SDK helper + - translateCopilotStudioPayload() from @armoriq/sdk + - axios → POST /iap/sdk/enforce on ArmorIQ backend ← single backend call +ArmorIQ backend (conmap-auto, unchanged) + → returns {allowed, action, reason} + ← back to MS as {action: allow|block} +``` + +#### What's where now + +| Repo / branch | What landed | +|---|---| +| `armoriq/armorCopilot` (this repo) — `feat/armorcopilot-ms-skeleton` | `packages/armorcopilot-ms/` Express service (5 source files + Dockerfile + README + .env.example) | +| `armoriq/armoriq-sdk-customer-ts` — `feat/microsoft-copilot-integration` (off `dev`) | New `src/integrations/microsoft_copilot.ts` (HMAC verify + payload translator + decision mapper) + index exports + version bump 0.3.3 → 0.4.0 | +| `armoriq/conmap-auto` — `feat/copilot-studio-webhook-poc` | Reset to ONLY commit `361d90b` (lint-staged setup for husky — useful repo improvement). All previous webhook/tenant/migration code reverted. Staging DB rolled back (`copilot_studio_tenants` table dropped). | + +#### Files in `armorCopilot/packages/armorcopilot-ms/` + +``` +package.json Express + @armoriq/sdk + axios deps. Targets @armoriq/sdk@^0.4.0 (must be published first) +tsconfig.json ES2022, CommonJS, strict +Dockerfile Two-stage Node 20 alpine build for Cloud Run +.env.example ARMORIQ_API_KEY, ARMORIQ_BACKEND_ENDPOINT, COPILOT_STUDIO_DEFAULT_SECRET, COPILOT_STUDIO_TENANT__SECRET +README.md Architecture notes + local dev + Cloud Run deploy steps +src/ +├── index.ts Express bootstrap. GET /health, POST /analyze-tool-execution/:tenantId +└── enforce.ts Thin axios wrapper around /iap/sdk/enforce. TODO: replace with SDK's client.enforceOnce() once the SDK exposes plan-less enforce +``` + +#### SDK additions (`armoriq-sdk-customer-ts/src/integrations/microsoft_copilot.ts`) + +- `verifyCopilotStudioSignature({ rawBody, signature, timestamp, secret })` — pure crypto HMAC verify with 5min skew tolerance, constant-time compare +- `translateCopilotStudioPayload(payload)` — MS shape → `{ toolName, args, ...meta }` +- `toCopilotStudioDecision(enforceResult)` — wraps SDK enforce result into MS-shaped `{ action, reason? }` +- All exported from `@armoriq/sdk` top-level + +#### Pending — design discussion (Week 3 priority) + +The SDK's `session.enforce()` requires a registered plan via `startPlan()`. Copilot Studio's per-tool-call interception doesn't have an upfront plan concept. For now, `packages/armorcopilot-ms/src/enforce.ts` makes a manual axios call to `/iap/sdk/enforce` with a synthetic intent token. Proper fix: add `client.enforceOnce(tool, args)` to the SDK so armorcopilot-ms can drop the manual axios call. + +Filed in armorCopilot tracker. Don't ship MVP without this. + +Modified: `enterprise/conmap-auto/src/app.module.ts` (registered `CopilotStudioModule`) + +#### Schema changes + +`prisma/schema.prisma` — added: +- `CopilotStudioTenant` model: id, orgId, name, hmacSecret, status, lastSeenAt, createdAt, updatedAt; unique (orgId, name); Organization relation +- `CopilotStudioTenantStatus` enum: `active`, `disabled` +- Relation in `Organization` model: `copilotStudioTenants CopilotStudioTenant[]` + +Migration: `prisma/migrations/20260522_add_copilot_studio_tenants/migration.sql` — committed to git. Applied to staging DB out-of-band via `prisma db execute` + manual `_prisma_migrations` row (because the existing migrations history has unrelated drift that blocks `migrate dev` locally). + +#### Verification status + +`tsc --noEmit` clean on both repos. End-to-end live testing pending: +1. Publish `@armoriq/sdk@0.4.0` (or use local file: link) +2. `npm install && npm run dev` in `packages/armorcopilot-ms/` +3. Smoke test: signed POST → 200 `{"action":"allow"}` (existing API key + COPILOT_STUDIO_DEFAULT_SECRET set in `.env`) +4. Production deploy: `gcloud run deploy armorcopilot-ms` (see README) + +#### Pending finish (housekeeping) + +1. Open 3 PRs (conmap-auto lint-staged, sdk microsoft_copilot integration, armorCopilot package) +2. Publish SDK 0.4.0 (or 0.4.0-dev) to npm so `armorcopilot-ms` can resolve via semver +3. Add `client.enforceOnce()` to SDK (Week 3 priority — replaces the manual axios wrap in `src/enforce.ts`) + +--- + +## 5. What's next (week-by-week) + +### Week 1 (DONE 2026-05-21) +- [x] Phase 0: repo + tracking issues +- [x] Phase A.8: backend PoC stub +- [x] PoC validation: signed POST returns `{"action":"allow"}` in 7.7ms +- [x] Commit PoC (commit 9503cee) +- [ ] Open draft PR against `dev` referencing #247 +- [ ] Register PoC URL in a test Copilot Studio tenant (Hari needs Microsoft side access) + +### Week 2 — Tenant onboarding (DONE 2026-05-22, ahead of schedule) +- [x] DB schema: `copilot_studio_tenants` table + `CopilotStudioTenantStatus` enum +- [x] Prisma migration (applied via `prisma db execute` due to unrelated migration drift) +- [x] `POST /copilot-studio/tenants` endpoint (JwtAuthGuard) — registers + returns webhook URL + HMAC secret +- [x] `GET /copilot-studio/tenants` admin list endpoint +- [x] `DELETE /copilot-studio/tenants/:tenantId` soft revoke +- [x] Wire `HmacAuthGuard.resolveTenantSecret()` to DB (with env fallback for dev) +- [ ] Frontend page on `platform.armoriq.ai/products/armor-copilot` for tenant registration + - Reuse existing armorClaude / armorCodex product page patterns (see `armorIQ-Frontend`) + - Tracking: not started — separate frontend PR + +### Week 3 — MVP (Phase A.9) +- [ ] Replace stub `decision.service.ts` with real `IapPolicyDecisionService.evaluate()` call + - Map MS payload → `IapEnforceRequest` shape (see `enterprise/conmap-auto/src/iap/dto/*`) + - Resolve org/agent from `tenantId` + `agentMetadata` +- [ ] Audit pipeline: call `enqueueAudit(dto)` after every decision +- [ ] Latency budget: instrument p95, target <400ms (Microsoft's cap is 1000ms) +- [ ] Load test: k6 script against `/copilot-studio/analyze-tool-execution/:tenantId` +- [ ] Admin dashboard surface: filter audit feed by `productKind: "armor-copilot"` +- [ ] Update plan + tracking issue with results + +### Week 4 — First customer onboarding +- [ ] Pick a friendly customer running Copilot Studio agents +- [ ] Walk them through: generate webhook URL, paste into Copilot Studio admin, confirm health check passes +- [ ] Run a test deny rule, confirm block + reason string in Copilot Studio UI + +### Phase B — ArmorCopilot-GH (sequenced AFTER MS MVP) +**Do not start until Phase A.9 is done.** Estimated effort: ~1 week. + +- [ ] File tracking issue: `armoriq/armorCopilot: tracking: ArmorCopilot-GH (gh copilot CLI hooks port)` +- [ ] Port armorCodex `scripts/lib/*` → `packages/armorcopilot-gh/scripts/lib/*` (see file-by-file map in [ARMORCOPILOT_PLAN.md](/Users/hariharasudhan/Armoriq/ARMORCOPILOT_PLAN.md) section B.1) +- [ ] Adapt hook payload parsing: Codex shape → GH Copilot CLI shape +- [ ] Write `install_armorcopilot_gh.sh` +- [ ] Submit to community list (if it exists) + document install on armoriq.ai + +--- + +## 6. Reference: cross-product reuse + +When implementing ArmorCopilot, lean heavily on what's already shipped: + +### From `conmap-auto` (for backend logic) +- `enterprise/conmap-auto/src/iap/iap-policy-decision.service.ts` — full policy engine, agent scoping, OPA eval +- `enterprise/conmap-auto/src/iap/iap-sdk.service.ts` — pattern for HTTP-driven enforcement (closest analog to our webhook) +- `enterprise/conmap-auto/src/iap/iap.controller.ts` — controller pattern + auth guards +- `enterprise/conmap-auto/src/iap/dto/*.ts` — DTO patterns +- `enterprise/conmap-auto/CLAUDE.md` — IAP/delegation/SDK consumer reference + gotchas + +### From `armorCodex` (for ArmorCopilot-GH, Phase B only) +- `armorCodex/plugins/armorcodex/scripts/lib/*.mjs` — ~85% portable. See port table in [ARMORCOPILOT_PLAN.md](/Users/hariharasudhan/Armoriq/ARMORCOPILOT_PLAN.md) section B.1. +- `armorCodex/install_armorcodex.sh` — template for `install_armorcopilot_gh.sh` +- `armorCodex/test-local.sh` — local-stack verification harness + +### From `armorClaude` (for ArmorCopilot-GH, reference only) +- `armorClaude/scripts/lib/audit-wal.mjs` — original async WAL pattern (already ported to armorCodex) +- `armorClaude/scripts/lib/daemon.mjs` — separate-process daemon (we use embedded flusher instead for Codex; same pattern likely for GH) + +--- + +## 7. Key reference docs + +- **Plan doc (full):** [/Users/hariharasudhan/Armoriq/ARMORCOPILOT_PLAN.md](/Users/hariharasudhan/Armoriq/ARMORCOPILOT_PLAN.md) +- **CEO brief:** [/Users/hariharasudhan/Armoriq/MEETING_BRIEF_HUI_2026-05-21.md](/Users/hariharasudhan/Armoriq/MEETING_BRIEF_HUI_2026-05-21.md) +- **Project CLAUDE.md (root):** [/Users/hariharasudhan/Armoriq/CLAUDE.md](/Users/hariharasudhan/Armoriq/CLAUDE.md) +- **conmap-auto CLAUDE.md:** [/Users/hariharasudhan/Armoriq/enterprise/conmap-auto/CLAUDE.md](/Users/hariharasudhan/Armoriq/enterprise/conmap-auto/CLAUDE.md) +- **Microsoft Copilot Studio external security webhooks docs:** https://learn.microsoft.com/en-us/microsoft-copilot-studio/external-security-webhooks-interface-developers +- **GitHub Copilot CLI hooks reference (for Phase B):** https://docs.github.com/en/copilot/reference/hooks-configuration + +--- + +## 8. Conventions + project rules + +These come from [/Users/hariharasudhan/Armoriq/CLAUDE.md](/Users/hariharasudhan/Armoriq/CLAUDE.md). Critical ones: + +- **Issue-first hard rule:** open a tracking issue BEFORE starting work — even info-only tracking issues +- **No em-dashes in user-facing copy** (use plain hyphens or restructure) — applies to docs, UI strings, blog posts +- **Use proper Prisma migrations** (`npx prisma migrate dev`) — never `npx prisma db push` +- **Reuse existing components and code** — don't create new ones if existing can be used +- **Don't generate comments** unless absolutely necessary +- **No emojis in code or comments** +- **Use `conda activate meta` if needed** for Python tasks +- **Always delete debug or temp scripts** when done + +### ArmorCopilot-specific conventions + +- Brand the product as **ArmorCopilot** in UI / docs (exact casing) +- Backend logs / decision service use `[copilot-studio]` prefix +- Each tenant has unique webhook URL: `/copilot-studio/analyze-tool-execution/` +- HMAC headers: `X-ArmorCopilot-Signature` (sha256 hex) + `X-ArmorCopilot-Timestamp` (unix seconds) +- 5min timestamp drift tolerance +- Decision response shape (Microsoft-defined): `{ action: "block" | "allow", reason?: string }` + +--- + +## 9. How to resume work in a new session + +1. Open this file (`/Users/hariharasudhan/Armoriq/armorCopilot/SESSION_CONTEXT.md`) +2. Read it top to bottom (this section included) +3. Verify current state matches: + ```bash + # Confirm PoC stub still exists + ls /Users/hariharasudhan/Armoriq/enterprise/conmap-auto/src/copilot-studio/ + + # Check branch state in conmap-auto + git -C /Users/hariharasudhan/Armoriq/enterprise/conmap-auto branch --show-current + # Expect: feat/copilot-studio-webhook-poc + + # Confirm health endpoint live (only if conmap-auto running) + curl http://localhost:3000/copilot-studio/health + ``` +4. Pick up at "What's next" section above based on what's still pending +5. If anything's changed since this doc was written, update this doc as you go + +--- + +## 10. Open questions (for Hari to answer / ask) + +- [ ] Does Hari have access to a Microsoft Copilot Studio test tenant for PoC URL registration? +- [ ] Should ArmorCopilot-MS launch with a free tier or behind paid plan? (talk to Hui) +- [ ] Do we want to file an issue on `microsoft/vscode` for the proposed permission API to push it toward GA? +- [ ] Frontend page: reuse the existing product page pattern from armorClaude/armorCodex or design a new one for tenant URL/secret display? (Stitch MCP if new design needed) + +--- + +## 11. Decision log (additions go here as new sessions happen) + +| Date | Session | Decision | Why | +|---|---|---|---| +| 2026-05-21 | Initial | Target MS Copilot Studio (not M365), sequence GH after MVP | See section 3 | +| 2026-05-21 | Initial | Host webhook in conmap-auto (not standalone service) | Reuses IapPolicyDecisionService + audit + auth infra; existing `/iap/sdk/enforce` already meets <1s budget | +| 2026-05-21 | Initial | Multi-tenant via path param `/analyze-tool-execution/:tenantId` | Each customer gets unique URL + HMAC secret | +| 2026-05-21 | Initial | Plain HMAC secret in DB (not KMS-encrypted) for v1 | PoC + MVP scope; documented as future hardening — secret cannot be hashed since we need it raw for HMAC verify | +| 2026-05-22 | Week 2 | lint-staged for husky pre-commit (vs whole-repo lint) | Repo has 3177 pre-existing lint errors that block every local commit; lint-staged scopes hook to staged files. Repo-wide cleanup tracked separately | +| 2026-05-22 | Week 2 | Applied migration via `prisma db execute` instead of `migrate dev` | Existing migrations history has unrelated drift (CREATE INDEX CONCURRENTLY can't run in shadow DB transaction); `migrate dev` rejects all new migrations until drift resolved | +| 2026-05-22 | Week 2 review | Dropped tenant CRUD endpoints; kept tenant table | User flagged that Week 2 was over-built relative to ArmorClaude/Codex (which use only the existing SDK). The TABLE has to stay — MS uses HMAC shared secrets per integration and the existing api_keys table stores only hashes. The CRUD endpoints (register/list/revoke) were unnecessary for MVP; can be re-added as a single one-shot endpoint or CLI when first customer needs onboarding UX | +| 2026-05-22 | Week 2 review | Keep webhook in conmap-auto (not standalone in armorCopilot repo) | Two-hop architecture (standalone webhook → SDK → conmap-auto) doubles network latency and threatens MS's 1000ms cap. ArmorClaude/Codex are local plugins so location was free; ArmorCopilot is server-to-server with hard deadline | + +--- + +*End of SESSION_CONTEXT.md. If you've read this far in a new session, you're ready to pick up where the last session left off.* diff --git a/packages/armorcopilot-ms/.env.example b/packages/armorcopilot-ms/.env.example new file mode 100644 index 0000000..37f6c18 --- /dev/null +++ b/packages/armorcopilot-ms/.env.example @@ -0,0 +1,17 @@ +# Port to listen on (Cloud Run sets this automatically to 8080) +PORT=8080 + +# ArmorIQ backend that handles policy enforcement +# Production: https://api.armoriq.ai +# Staging: https://staging-api.armoriq.ai +# Local dev: http://127.0.0.1:3000 +ARMORIQ_BACKEND_ENDPOINT=https://api.armoriq.ai + +# Your ArmorIQ API key (sk-***) +# Generate at https://platform.armoriq.ai +ARMORIQ_API_KEY= + +# Single-tenant HMAC secret. The customer pastes this into Copilot Studio admin +# alongside the webhook URL. Per-tenant secrets supported via +# COPILOT_STUDIO_TENANT__SECRET=. +COPILOT_STUDIO_DEFAULT_SECRET= diff --git a/packages/armorcopilot-ms/Dockerfile b/packages/armorcopilot-ms/Dockerfile new file mode 100644 index 0000000..94926d2 --- /dev/null +++ b/packages/armorcopilot-ms/Dockerfile @@ -0,0 +1,18 @@ +FROM node:20-alpine AS build +WORKDIR /app +COPY package.json ./ +RUN npm install --omit=dev --no-package-lock +COPY tsconfig.json ./ +COPY src ./src +RUN npm install --no-save typescript@5.6.0 +RUN npx tsc -p tsconfig.json + +FROM node:20-alpine +WORKDIR /app +ENV NODE_ENV=production +COPY package.json ./ +RUN npm install --omit=dev --no-package-lock +COPY --from=build /app/dist ./dist +USER node +EXPOSE 8080 +CMD ["node", "dist/index.js"] diff --git a/packages/armorcopilot-ms/README.md b/packages/armorcopilot-ms/README.md new file mode 100644 index 0000000..c275e99 --- /dev/null +++ b/packages/armorcopilot-ms/README.md @@ -0,0 +1,62 @@ +# @armoriq/armorcopilot-ms + +Webhook receiver for Microsoft Copilot Studio's external security webhook (`/analyze-tool-execution`). Deployed as a small standalone HTTPS service that: + +1. Verifies the HMAC signature MS Copilot Studio attaches to every webhook call (via `verifyCopilotStudioSignature` from `@armoriq/sdk`) +2. Translates the MS payload to ArmorIQ's tool-call shape +3. Calls the ArmorIQ backend `/iap/sdk/enforce` to get the policy decision +4. Returns `{action: "allow" | "block", reason?}` to MS Copilot Studio within 1000ms + +## Local dev + +```bash +cp .env.example .env +# fill in ARMORIQ_API_KEY + COPILOT_STUDIO_DEFAULT_SECRET +npm install +npm run dev # uses tsx watch, listens on :8080 +``` + +Smoke test against a local backend: + +```bash +# Health +curl http://localhost:8080/health + +# Signed allow call (use the COPILOT_STUDIO_DEFAULT_SECRET from .env) +SECRET="" +TS=$(date +%s) +BODY='{"toolDefinition":{"name":"send_email"},"toolInputValues":{"to":"a@b"}}' +SIG=$(node -e "const c=require('crypto'); console.log(c.createHmac('sha256','$SECRET').update('$TS.'+'$BODY').digest('hex'))") +curl -X POST http://localhost:8080/analyze-tool-execution/single-tenant \ + -H "Content-Type: application/json" \ + -H "X-ArmorCopilot-Signature: sha256=$SIG" \ + -H "X-ArmorCopilot-Timestamp: $TS" \ + -d "$BODY" +``` + +## Deploy (Cloud Run) + +```bash +gcloud builds submit --tag gcr.io//armorcopilot-ms +gcloud run deploy armorcopilot-ms \ + --image gcr.io//armorcopilot-ms \ + --region us-east1 \ + --allow-unauthenticated \ + --min-instances=1 \ + --set-env-vars=ARMORIQ_BACKEND_ENDPOINT=https://api.armoriq.ai \ + --set-secrets=ARMORIQ_API_KEY=armorcopilot-api-key:latest,COPILOT_STUDIO_DEFAULT_SECRET=armorcopilot-studio-secret:latest +``` + +The HTTPS URL `gcloud run deploy` prints is what gets pasted into the Copilot Studio admin UI. + +## Architecture notes (read before changing) + +- **Why not put this in conmap-auto?** We did briefly. The webhook is server-side facing Microsoft's planner, so a local plugin pattern (like ArmorClaude / ArmorCodex) doesn't apply. We tried hosting it inline in conmap-auto for latency, but moved out to keep the backend surface clean and follow the "every product is its own deploy" pattern. +- **HMAC verify lives in the SDK** (`@armoriq/sdk/integrations/microsoft_copilot.ts`) so any future ArmorIQ service that fronts Copilot Studio can reuse it. +- **Policy enforce uses the existing `/iap/sdk/enforce` endpoint.** Today we wrap the call in `src/enforce.ts` with a synthetic intent token because the SDK's `session.enforce()` requires a registered plan, which doesn't fit MS's per-call interception model. TODO: add `client.enforceOnce()` to the SDK so this file can drop the manual axios call. +- **Multi-tenant via env vars for v1.** `COPILOT_STUDIO_TENANT__SECRET` for per-integration secrets, otherwise `COPILOT_STUDIO_DEFAULT_SECRET`. Promote to Secret Manager when there's a third customer. + +## Refs + +- Microsoft Copilot Studio external security webhooks: https://learn.microsoft.com/en-us/microsoft-copilot-studio/external-security-webhooks-interface-developers +- Parent tracking issue: [armoriq/armorCopilot#1](https://github.com/armoriq/armorCopilot/issues/1) diff --git a/packages/armorcopilot-ms/package-lock.json b/packages/armorcopilot-ms/package-lock.json new file mode 100644 index 0000000..569a397 --- /dev/null +++ b/packages/armorcopilot-ms/package-lock.json @@ -0,0 +1,1708 @@ +{ + "name": "@armoriq/armorcopilot-ms", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@armoriq/armorcopilot-ms", + "version": "0.1.0", + "dependencies": { + "@armoriq/sdk": "file:../../../armoriq-sdk-customer-ts", + "axios": "^1.7.0", + "express": "^4.21.0" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^20.0.0", + "tsx": "^4.19.0", + "typescript": "^5.6.0" + } + }, + "../../../armoriq-sdk-customer-ts": { + "name": "@armoriq/sdk", + "version": "0.3.3", + "license": "MIT", + "dependencies": { + "axios": "^1.7.0", + "js-yaml": "^4.1.1" + }, + "bin": { + "armoriq": "dist/cli/index.js" + }, + "devDependencies": { + "@types/jest": "^29.5.0", + "@types/js-yaml": "^4.0.9", + "@types/node": "^20.0.0", + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.57.0", + "jest": "^29.7.0", + "prettier": "^3.2.0", + "ts-jest": "^29.1.0", + "typescript": "^5.4.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@armoriq/sdk": { + "resolved": "../../../armoriq-sdk-customer-ts", + "link": true + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsx": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz", + "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/packages/armorcopilot-ms/package.json b/packages/armorcopilot-ms/package.json new file mode 100644 index 0000000..2484c91 --- /dev/null +++ b/packages/armorcopilot-ms/package.json @@ -0,0 +1,24 @@ +{ + "name": "@armoriq/armorcopilot-ms", + "version": "0.1.0", + "private": true, + "description": "ArmorCopilot-MS webhook service. Receives Microsoft Copilot Studio external security webhook calls, verifies HMAC via @armoriq/sdk, evaluates policy via ArmorIQ backend.", + "main": "dist/index.js", + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node dist/index.js", + "dev": "tsx watch src/index.ts", + "lint": "tsc --noEmit" + }, + "dependencies": { + "@armoriq/sdk": "^0.4.0", + "axios": "^1.7.0", + "express": "^4.21.0" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^20.0.0", + "tsx": "^4.19.0", + "typescript": "^5.6.0" + } +} diff --git a/packages/armorcopilot-ms/src/enforce.ts b/packages/armorcopilot-ms/src/enforce.ts new file mode 100644 index 0000000..4b66718 --- /dev/null +++ b/packages/armorcopilot-ms/src/enforce.ts @@ -0,0 +1,73 @@ +/** + * Thin wrapper that bridges a Copilot Studio tool invocation to the + * ArmorIQ policy enforce endpoint. + * + * Today this posts directly to `/iap/sdk/enforce` with a stub intent + * token. The SDK's session.enforce() flow requires a registered plan + * (via startPlan), which doesn't map cleanly to Copilot Studio's + * per-call interception model. Refactor TODO: expose a plan-less enforce + * method on ArmorIQClient so this file can drop the manual axios call. + * + * Refs: armoriq/armoriq-sdk-customer-ts → add `client.enforceOnce()`. + */ +import axios from 'axios'; + +export interface EnforceArgs { + backend: string; + apiKey: string; + tenantId: string; + tool: string; + args: Record; + userMessage?: string; + agentMetadata?: Record; + userMetadata?: Record; +} + +export interface EnforceResult { + allowed: boolean; + action?: 'allow' | 'block' | 'hold'; + reason?: string; + matchedPolicy?: string; +} + +export async function enforceToolViaBackend( + ea: EnforceArgs, +): Promise { + const payload = { + tool: ea.tool, + arguments: ea.args, + intent_token: { plan: { goal: ea.userMessage ?? '', steps: [] } }, + policy_snapshot: null, + user_email: + (ea.userMetadata?.email as string | undefined) ?? undefined, + agent_id: + (ea.agentMetadata?.id as string | undefined) ?? 'copilot-studio', + source: { product: 'armorcopilot-ms', tenantId: ea.tenantId }, + }; + + const resp = await axios.post(`${ea.backend}/iap/sdk/enforce`, payload, { + headers: { + 'X-API-Key': ea.apiKey, + 'Content-Type': 'application/json', + }, + timeout: 800, + validateStatus: (s) => s >= 200 && s < 500, + }); + + const data = (resp.data ?? {}) as Record; + const allowed = data.allowed !== false; + const action = + (data.enforcementAction as 'allow' | 'block' | 'hold' | undefined) ?? + (data.action as 'allow' | 'block' | 'hold' | undefined) ?? + (allowed ? 'allow' : 'block'); + return { + allowed, + action, + reason: + (data.reason as string | undefined) ?? (data.message as string | undefined), + matchedPolicy: + typeof data.matched_policy === 'object' + ? ((data.matched_policy as { name?: string }).name ?? undefined) + : ((data.matched_policy as string | undefined) ?? undefined), + }; +} diff --git a/packages/armorcopilot-ms/src/index.ts b/packages/armorcopilot-ms/src/index.ts new file mode 100644 index 0000000..367a77a --- /dev/null +++ b/packages/armorcopilot-ms/src/index.ts @@ -0,0 +1,107 @@ +import express from 'express'; +import { + verifyCopilotStudioSignature, + translateCopilotStudioPayload, + toCopilotStudioDecision, + CopilotStudioPayload, +} from '@armoriq/sdk'; +import { enforceToolViaBackend } from './enforce'; + +const PORT = Number(process.env.PORT ?? 8080); +const ARMORIQ_BACKEND = + process.env.ARMORIQ_BACKEND_ENDPOINT ?? 'https://api.armoriq.ai'; +const ARMORIQ_API_KEY = process.env.ARMORIQ_API_KEY ?? ''; +const COPILOT_STUDIO_DEFAULT_SECRET = + process.env.COPILOT_STUDIO_DEFAULT_SECRET ?? ''; + +if (!ARMORIQ_API_KEY) { + console.warn( + '[armorcopilot-ms] ARMORIQ_API_KEY not set — every request will fail enforcement', + ); +} +if (!COPILOT_STUDIO_DEFAULT_SECRET) { + console.warn( + '[armorcopilot-ms] COPILOT_STUDIO_DEFAULT_SECRET not set — HMAC verify will reject every request', + ); +} + +const app = express(); + +app.use( + express.json({ + limit: '256kb', + verify: (req, _res, buf) => { + (req as express.Request & { rawBody?: Buffer }).rawBody = buf; + }, + }), +); + +app.get('/health', (_req, res) => { + res.status(200).json({ + status: 'ok', + service: 'armorcopilot-ms', + version: '0.1.0', + }); +}); + +app.post('/analyze-tool-execution/:tenantId', async (req, res) => { + const tenantId = req.params.tenantId; + const signature = req.header('X-ArmorCopilot-Signature'); + const timestamp = req.header('X-ArmorCopilot-Timestamp'); + const rawBody = + (req as express.Request & { rawBody?: Buffer }).rawBody ?? + Buffer.from(JSON.stringify(req.body ?? {})); + + const verify = verifyCopilotStudioSignature({ + rawBody, + signature: signature ?? '', + timestamp: timestamp ?? '', + secret: resolveTenantSecret(tenantId), + }); + if (!verify.ok) { + return res + .status(401) + .json({ action: 'block', reason: `hmac:${verify.reason}` }); + } + + const translated = translateCopilotStudioPayload( + req.body as CopilotStudioPayload, + ); + + const start = Date.now(); + try { + const enforced = await enforceToolViaBackend({ + backend: ARMORIQ_BACKEND, + apiKey: ARMORIQ_API_KEY, + tenantId, + tool: translated.toolName, + args: translated.args, + userMessage: translated.userMessage, + agentMetadata: translated.agentMetadata, + userMetadata: translated.userMetadata, + }); + const decision = toCopilotStudioDecision(enforced); + console.log( + `[armorcopilot-ms] tenant=${tenantId} tool=${translated.toolName} action=${decision.action} took=${Date.now() - start}ms`, + ); + return res.status(200).json(decision); + } catch (err) { + console.error( + `[armorcopilot-ms] enforce failed for tenant=${tenantId}: ${(err as Error).message}`, + ); + return res + .status(200) + .json({ action: 'allow', reason: 'enforce-unavailable' }); + } +}); + +function resolveTenantSecret(tenantId: string): string { + const envKey = `COPILOT_STUDIO_TENANT_${tenantId.replace(/-/g, '_').toUpperCase()}_SECRET`; + return process.env[envKey] ?? COPILOT_STUDIO_DEFAULT_SECRET; +} + +app.listen(PORT, () => { + console.log( + `[armorcopilot-ms] listening on :${PORT} backend=${ARMORIQ_BACKEND}`, + ); +}); diff --git a/packages/armorcopilot-ms/tsconfig.json b/packages/armorcopilot-ms/tsconfig.json new file mode 100644 index 0000000..5b20b2d --- /dev/null +++ b/packages/armorcopilot-ms/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "resolveJsonModule": true, + "strict": true, + "skipLibCheck": true, + "declaration": false, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*.ts"] +}