feat: armorcopilot-ms Express webhook service skeleton#2
feat: armorcopilot-ms Express webhook service skeleton#2Harihara04sudhan wants to merge 1 commit into
Conversation
… Copilot Studio
Scaffolds @armoriq/armorcopilot-ms, the standalone Cloud Run service
that fronts Microsoft Copilot Studio's external security webhook
(/analyze-tool-execution). Mirrors the ArmorClaude/ArmorCodex pattern:
own repo, own deploy, talks to ArmorIQ backend through @armoriq/sdk.
Surface:
GET /health liveness probe
POST /analyze-tool-execution/:tenantId MS webhook receiver
Flow:
1. verifyCopilotStudioSignature() from @armoriq/sdk verifies HMAC
against per-tenant secret (env-var resolution today: per-tenant
COPILOT_STUDIO_TENANT_<id>_SECRET or COPILOT_STUDIO_DEFAULT_SECRET)
2. translateCopilotStudioPayload() from @armoriq/sdk reshapes the
MS payload into ArmorIQ's tool-call shape
3. enforceToolViaBackend() (src/enforce.ts) posts to /iap/sdk/enforce
with the customer's API key. Single backend call, p95 target <400ms
4. toCopilotStudioDecision() returns {action: allow|block} to MS
Targets @armoriq/sdk@^0.4.0 (must be published before npm install resolves).
Files:
package.json + tsconfig.json + Dockerfile (Node 20 alpine, two-stage)
.env.example with ARMORIQ_API_KEY + ARMORIQ_BACKEND_ENDPOINT +
COPILOT_STUDIO_DEFAULT_SECRET
README.md with local-dev + Cloud Run deploy steps
src/index.ts (Express bootstrap)
src/enforce.ts (thin axios wrapper, TODO: replace once SDK exposes
client.enforceOnce() for plan-less enforce)
Also updates SESSION_CONTEXT.md to reflect the final architecture after
reverting the earlier in-conmap-auto approach.
Refs #1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Scaffolds a new standalone Node/Express service (@armoriq/armorcopilot-ms) intended to receive Microsoft Copilot Studio’s /analyze-tool-execution/:tenantId webhook, verify HMAC signatures via @armoriq/sdk, translate the payload, and call ArmorIQ backend enforcement (/iap/sdk/enforce) before returning an allow/block decision.
Changes:
- Adds
packages/armorcopilot-mswith Express server (/health,/analyze-tool-execution/:tenantId) and an axios-based enforcement bridge. - Adds containerization + local dev artifacts (Dockerfile,
.env.example, README, TS config). - Adds a
SESSION_CONTEXT.mdarchitecture/session reference doc.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| SESSION_CONTEXT.md | Adds a session/architecture reference document for ArmorCopilot. |
| packages/armorcopilot-ms/tsconfig.json | TypeScript compiler configuration for the new service. |
| packages/armorcopilot-ms/src/index.ts | Express bootstrap + webhook handler with HMAC verification and decision response. |
| packages/armorcopilot-ms/src/enforce.ts | Axios wrapper that forwards tool calls to ArmorIQ backend enforcement. |
| packages/armorcopilot-ms/README.md | Local dev + Cloud Run deploy instructions and architecture notes. |
| packages/armorcopilot-ms/package.json | Declares dependencies/scripts for the new service. |
| packages/armorcopilot-ms/package-lock.json | Lockfile for the new service’s dependencies. |
| packages/armorcopilot-ms/Dockerfile | Two-stage build container for Cloud Run deployment. |
| packages/armorcopilot-ms/.env.example | Environment variable template for local/dev deployments. |
Files not reviewed (1)
- packages/armorcopilot-ms/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| | `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). | | ||
|
|
| | 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 | |
| } from '@armoriq/sdk'; | ||
| import { enforceToolViaBackend } from './enforce'; | ||
|
|
||
| const PORT = Number(process.env.PORT ?? 8080); |
| 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<string, unknown>; | ||
| const allowed = data.allowed !== false; | ||
| const action = | ||
| (data.enforcementAction as 'allow' | 'block' | 'hold' | undefined) ?? | ||
| (data.action as 'allow' | 'block' | 'hold' | undefined) ?? | ||
| (allowed ? 'allow' : 'block'); |
| "": { | ||
| "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", |
| RUN npm install --omit=dev --no-package-lock | ||
| COPY tsconfig.json ./ | ||
| COPY src ./src | ||
| RUN npm install --no-save typescript@5.6.0 |
| 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 |
|
Parking this PR — pivoting to GitHub Copilot CLI first per #1 architecture rethink. Branch Revisit this PR if/when we want a separate ArmorCopilot-MS product as Phase 2 (governance-only SKU). Until then, the work stays on the branch and stays unmerged. |
Summary
Scaffolds `@armoriq/armorcopilot-ms`, the standalone Cloud Run service that fronts Microsoft Copilot Studio's external security webhook (`/analyze-tool-execution`). This is the ArmorCopilot product itself — mirrors how ArmorClaude and ArmorCodex live in their own repos and call into the ArmorIQ backend via `@armoriq/sdk`.
Architecture
```
Microsoft Copilot Studio
→ HTTPS POST /analyze-tool-execution/:tenantId
@armoriq/armorcopilot-ms (this service, Cloud Run)
→ verifyCopilotStudioSignature() from @armoriq/sdk (HMAC verify, no backend call)
→ translateCopilotStudioPayload() from @armoriq/sdk (MS → ArmorIQ tool-call shape)
→ axios POST /iap/sdk/enforce on ArmorIQ backend (single backend call, p95 target <400ms)
ArmorIQ backend (conmap-auto, unchanged)
← {allowed, action, reason}
← {action: allow|block} back to MS
```
Why standalone (not in conmap-auto)
Earlier PoC hosted this directly in `conmap-auto`. Reverted because:
Endpoints
```
GET /health liveness probe
POST /analyze-tool-execution/:tenantId MS webhook receiver (HMAC-signed)
```
Files
```
packages/armorcopilot-ms/
├── package.json Express + @armoriq/sdk@^0.4.0 + axios
├── tsconfig.json ES2022, CommonJS, strict
├── Dockerfile Two-stage Node 20 alpine for Cloud Run
├── .env.example ARMORIQ_API_KEY + ARMORIQ_BACKEND_ENDPOINT + COPILOT_STUDIO_DEFAULT_SECRET
├── README.md Local dev + Cloud Run deploy walkthrough
└── src/
├── index.ts Express bootstrap, route handlers
└── enforce.ts Thin axios wrapper → /iap/sdk/enforce
```
Also updates `SESSION_CONTEXT.md` to reflect the final architecture after the in-conmap-auto pivot.
Test plan
Known follow-ups
Refs #1
🤖 Generated with Claude Code