- Notifications
You must be signed in to change notification settings - Fork 0
Ops capi hardening#46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -6,12 +6,12 @@ | ||
| # When PGL_LEDGER_URL is set, every sealed evidence record (Phase 7) is mirrored | ||
| # into gnomledger's append-only, hash-chained ledger. Leave empty to keep the | ||
| # local seal only. | ||
| PGL_LEDGER_URL= | ||
| PGL_LEDGER_URL=http://gnomledger-api-1:8001 | ||
| PGL_LEDGER_API_KEY= | ||
| PGL_LEDGER_TIMEOUT_MS=8000 | ||
| # --- Phase 6 execution bridge (Veklom BYOS MCP gateway) --- | ||
| BYOS_MCP_GATEWAY_URL= | ||
| BYOS_MCP_GATEWAY_URL=http://n13gp1nhrcdp0hvazvbnlxru-213557155694:8088/api/v2/invoke | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
With this configuration, every Useful? React with 👍 / 👎. | ||
| BYOS_INTERNAL_API_KEY= | ||
| COVENANT_EXEC_TIMEOUT_MS=10000 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| name: Mayhem DAST | ||
| on: | ||
| push: | ||
| branches: [ "main" ] | ||
| pull_request: | ||
| branches: [ "main" ] | ||
| workflow_dispatch: | ||
| jobs: | ||
| fuzz: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '20' | ||
| cache: 'npm' | ||
| - name: Install dependencies | ||
| run: npm ci | ||
| - name: Start cAPI Server in background | ||
| run: npm run dev & | ||
| env: | ||
| PORT: 3000 | ||
| - name: Wait for server to be ready | ||
| run: sleep 10 | ||
| - name: Mayhem for API | ||
| uses: ForAllSecure/mapi-action@v2 | ||
| with: | ||
| mayhem-token: ${{ secrets.MAYHEM_TOKEN }} | ||
| api-url: http://localhost:3000 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In this workflow, Useful? React with 👍 / 👎. | ||
| api-spec: openapi.json | ||
| duration: 300 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| version: '1.0' | ||
| api: | ||
| openapi: openapi.json | ||
| target: http://localhost:3000 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| { | ||
| "openapi": "3.0.0", | ||
| "info": { | ||
| "title": "cAPI Fuzz Target", | ||
| "version": "1.0.0" | ||
| }, | ||
| "servers": [ | ||
| { | ||
| "url": "http://localhost:3000" | ||
| } | ||
| ], | ||
| "paths": { | ||
| "/health": { | ||
| "get": { | ||
| "responses": { | ||
| "200": { | ||
| "description": "OK" | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "/api/v1/registry/heartbeat": { | ||
| "post": { | ||
| "requestBody": { | ||
| "content": { | ||
| "application/json": { | ||
| "schema": { | ||
| "type": "object", | ||
| "properties": { | ||
| "service": { | ||
| "type": "string" | ||
| }, | ||
| "status": { | ||
| "type": "string" | ||
| } | ||
Comment on lines
+30
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The heartbeat route validates a strict object containing only required Useful? React with 👍 / 👎. | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "responses": { | ||
| "200": { | ||
| "description": "OK" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| import crypto from "crypto"; | ||
| async function main() { | ||
| console.log("🚀 Initializing Outly cAPI Test Harness..."); | ||
| // 1. Calculate "Next Friday at 10:00 AM" | ||
| const now = new Date(); | ||
| const daysUntilFriday = (5 - now.getDay() + 7) % 7 || 7; // Ensure it's next Friday if today is Friday | ||
| const nextFriday = new Date(now.getFullYear(), now.getMonth(), now.getDate() + daysUntilFriday); | ||
| nextFriday.setHours(10, 0, 0, 0); // 10:00 AM | ||
| console.log(`📅 Target Appointment Date: ${nextFriday.toLocaleString()}`); | ||
| const payload = { | ||
| workspace_id: "wksp_outly_demo", | ||
| tenant_id: "tenant_acme_corp", | ||
| connection_id: crypto.randomUUID(), | ||
| connection_version: "1.0.0", | ||
| action_id: crypto.randomUUID(), | ||
| execution_id: crypto.randomUUID(), | ||
| actor_identity: { | ||
| actor_id: "agent-outly-scheduler", | ||
| actor_type: "agent", | ||
| public_key: "outly-demo-key" | ||
| }, | ||
| capability_id: "cap-outly-schedule", | ||
| capability_version: "1.0.0", | ||
| policy_version: "1.0.0", | ||
| nonce: crypto.randomBytes(16).toString("hex"), | ||
| idempotency_key: crypto.randomUUID(), | ||
| timestamp: new Date().toISOString(), | ||
| expires_at: new Date(Date.now() + 15 * 60 * 1000).toISOString(), | ||
| requested_side_effect: { | ||
| action: "schedule_appointment", | ||
| description: "Book Outly consultation appointment for next Friday at 10:00 AM", | ||
| lane: 1, // Lane 1 for auto-allow scheduling, Lane 2/3 for financial/critical ops | ||
| parameters: { | ||
| appointment_time: nextFriday.toISOString(), | ||
| attendees: ["client@example.com", "outly-rep@example.com"] | ||
| } | ||
| } | ||
| }; | ||
| console.log("\n📦 Payload Constructed:"); | ||
| console.log(JSON.stringify(payload, null, 2)); | ||
| console.log("\n📡 Submitting to cAPI (Governed Connection Layer) -> /api/outly/intercept"); | ||
| try { | ||
| const response = await fetch("https://capi.veklom.com/api/outly/intercept", { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Running this developer test script always submits its generated demo action to the production Useful? React with 👍 / 👎. | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify(payload) | ||
| }); | ||
| const result = await response.json(); | ||
| console.log(`\n⚖️ cAPI Decision Status: ${response.status}`); | ||
| if (response.ok) { | ||
| console.log("✅ Intercept Successful. Decision:"); | ||
| console.log(JSON.stringify(result, null, 2)); | ||
| console.log(`\n🔒 Cryptographic Evidence Sealed in PGL!`); | ||
| console.log(`PGL Entry Hash: ${result.evidence_reference?.entry_hash}`); | ||
| } else { | ||
| console.log("❌ Intercept Failed or Denied:"); | ||
| console.log(result); | ||
| } | ||
| } catch (error) { | ||
| console.error("Failed to connect to cAPI. Ensure the cAPI server is running on localhost:3002."); | ||
| console.error(error); | ||
| } | ||
| } | ||
| main(); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -2,28 +2,84 @@ import { NextResponse } from "next/server"; | ||
| import { evaluateProposedAction } from "@/lib/covenant/outly-gate"; | ||
| import { IntegrationUnavailable, postIntegration, requireIntegration } from "@/lib/covenant/integrations"; | ||
| import { proposedActionSchema, readJson } from "@/lib/covenant/validation"; | ||
| import { LockerphycerClient } from "@/lib/covenant/locker-client"; | ||
| export async function POST(req: Request) { | ||
| const parsed = await readJson(req, proposedActionSchema); | ||
| if ("error" in parsed) return NextResponse.json({ error: parsed.error }, { status: 400 }); | ||
| try { | ||
| const pglUrl = requireIntegration("PGL", process.env.PGL_LEDGER_URL); | ||
| const decision = await evaluateProposedAction(parsed.data); | ||
| const anchored = await postIntegration(`${pglUrl}/api/v1/ledger/events`, { | ||
| // 1. Send to PGL (Immutable Genome / Lineage Ledger) | ||
| const pglUrl = requireIntegration("PGL", process.env.PGL_LEDGER_URL); | ||
| const pglAnchored = await postIntegration(`${pglUrl}/api/v1/ledger/events`, { | ||
| agent_id: parsed.data.actor_identity.actor_id, | ||
| event_type: "custom", | ||
| actor: parsed.data.actor_identity.actor_id, | ||
| summary: `outly decision ${decision.decision}: ${parsed.data.action_id}`.slice(0, 255), | ||
| details: { source: "capi-outly", kind: "decision", action: parsed.data, decision }, | ||
| idempotency_key: parsed.data.idempotency_key, | ||
| }, process.env.PGL_LEDGER_API_KEY ? { "x-api-key": process.env.PGL_LEDGER_API_KEY } : undefined); | ||
| if (typeof anchored.event_id !== "string" || typeof anchored.event_hash !== "string") { | ||
| if (typeof pglAnchored.event_id !== "string" || typeof pglAnchored.event_hash !== "string") { | ||
| throw new IntegrationUnavailable("PGL returned no verifiable evidence reference"); | ||
| } | ||
| // 2. Send to Lockerphycer (Sovereign Security / Telemetry Layer) | ||
| const lockerphycerAnchored = await LockerphycerClient.registerAuditRecord({ | ||
| evidence_id: parsed.data.action_id, | ||
| connection_id: parsed.data.connection_id, | ||
| pgl_hash: pglAnchored.event_hash, // Bind the PGL hash to Lockerphycer's security audit! | ||
| seal_nonce: parsed.data.nonce, | ||
| timestamp: new Date().toISOString(), | ||
| who: { | ||
| agent_id: parsed.data.actor_identity.actor_id, | ||
| agent_public_key: parsed.data.actor_identity.public_key ?? "unverified", | ||
| owner_id: parsed.data.tenant_id, | ||
| }, | ||
| what: { | ||
| capability_id: parsed.data.capability_id, | ||
| capability_name: "outly_schedule", | ||
| action: parsed.data.requested_side_effect.action, | ||
| }, | ||
| when: { | ||
| requested_at: parsed.data.timestamp, | ||
| executed_at: new Date().toISOString(), | ||
| completed_at: new Date().toISOString(), | ||
| }, | ||
| why: { | ||
| policy_applied: parsed.data.policy_version, | ||
| policy_version: parsed.data.policy_version, | ||
| authorization_proof: "outly-gate", | ||
| request_context: "outly-intercept", | ||
| }, | ||
| how: { | ||
| method: "http", | ||
| endpoint: "/api/outly/intercept", | ||
| retry_count: 0, | ||
| }, | ||
| result: { | ||
| status: decision.decision === "ALLOW" ? "passed" : "denied", | ||
| output_hash: "", | ||
| output_size: 0, | ||
| execution_time_ms: 10, | ||
| }, | ||
| compliance: { | ||
| audit_logged: true, | ||
| regulatory_category: "schedule", | ||
| data_classification: "internal", | ||
| retention_policy: "7y", | ||
| } | ||
| }); | ||
| return NextResponse.json({ | ||
| ...decision, | ||
| evidence_reference: { evidence_id: anchored.event_id, entry_hash: anchored.event_hash, ledger: "pgl" }, | ||
| evidence_reference: { | ||
| evidence_id: parsed.data.action_id, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For every successful intercept, this replaces the actual Useful? React with 👍 / 👎. | ||
| entry_hash: pglAnchored.event_hash, | ||
| ledger: "dual-pgl-lockerphycer" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Lockerphycer is unavailable or returns any non-2xx response, Useful? React with 👍 / 👎. | ||
| }, | ||
| }); | ||
| } catch (error) { | ||
| const status = error instanceof IntegrationUnavailable ? 503 : 502; | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file instructs developers to copy it to
.env.localand explicitly says this value should be empty to retain local-only sealing, but the new default enables a Docker-network-specific hostname with no API key. A normal local setup copied from the template therefore attempts an unavailable or unauthorized external ledger instead of remaining disabled, changing Outly calls from the intended clear unconfigured response into network/integration failures and generating failed-forwarding records throughout the runtime.Useful? React with 👍 / 👎.