Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.example
Original file line numberDiff line numberDiff line change
Expand Up@@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Leave the optional PGL integration unset in the template

The file instructs developers to copy it to .env.local and 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 👍 / 👎.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Match the BYOS endpoint to the bridge payload

With this configuration, every mcp:// capability posts the JSON-RPC tools/call payload from src/lib/covenant/mcp-bridge.ts to /api/v2/invoke. The checked-in Interlink router binds /api/v2/invoke to an InvocationRequest requiring capability_id, arguments, and context, while its JSON-RPC handler is a different route, so the configured endpoint rejects the bridge payload before execution; configure a compatible MCP endpoint or serialize the REST invocation contract.

Useful? React with 👍 / 👎.

BYOS_INTERNAL_API_KEY=
COVENANT_EXEC_TIMEOUT_MS=10000

Expand Down
38 changes: 38 additions & 0 deletions .github/workflows/mayhem-dast.yml
Original file line numberDiff line numberDiff 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Point Mayhem at the port started by the workflow

In this workflow, npm run dev expands to next dev -p 3003, while Mayhem is configured to connect to port 3000. Next's -p, --port <port> option explicitly selects the listening port, so the CLI argument takes precedence over the step's PORT=3000; after the fixed sleep, the action probes a port with no cAPI server and the new DAST job cannot exercise the API.

Useful? React with 👍 / 👎.

api-spec: openapi.json
duration: 300
4 changes: 4 additions & 0 deletions Mayhemfile
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
version: '1.0'
api:
openapi: openapi.json
target: http://localhost:3000
2 changes: 1 addition & 1 deletion RUNTIME_PATCH.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,7 +93,7 @@ const response = await getEngine().signAndProcess({ ... });
Add to `.env.local` (never commit):

```env
BYOS_MCP_GATEWAY_URL=https://api.veklom.com/api/v1/mcp
BYOS_MCP_GATEWAY_URL=http://n13gp1nhrcdp0hvazvbnlxru-213557155694:8088/api/v1/mcp
BYOS_INTERNAL_API_KEY=your_internal_key_here
COVENANT_EXEC_TIMEOUT_MS=10000
```
Expand Down
49 changes: 49 additions & 0 deletions openapi.json
Original file line numberDiff line numberDiff 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Describe the heartbeat payload accepted by the route

The heartbeat route validates a strict object containing only required service_name, but the new OpenAPI schema advertises service and status and does not require either field. Consequently every request generated from the documented properties is rejected with 400, so Mayhem cannot reach the heartbeat logic and may report the undocumented response instead of fuzzing the intended endpoint.

Useful? React with 👍 / 👎.

}
}
}
}
},
"responses": {
"200": {
"description": "OK"
}
}
}
}
}
}
63 changes: 63 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,9 +3,9 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "next dev -p 3003",
"build": "next build",
"start": "next start",
"start": "next start -p 3003",
"lint": "next lint",
"test": "vitest run"
},
Expand All@@ -19,6 +19,7 @@
"zod": "^4.4.3"
},
"devDependencies": {
"@playwright/test": "^1.62.0",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand Down
Binary file addedpublic/favicon.ico
Binary file not shown.
Binary file addedpublic/og-image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file addedpublic/twitter-card.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions scripts/outly-booking-test.ts
Original file line numberDiff line numberDiff 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", {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the booking test harness off production by default

Running this developer test script always submits its generated demo action to the production capi.veklom.com endpoint, even though its failure message tells the user to start a localhost server. Each invocation can therefore add synthetic Outly decisions to the production immutable PGL and Lockerphycer audit trail; use an environment-provided base URL with a localhost default and require an explicit opt-in for 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();
64 changes: 60 additions & 4 deletions src/app/api/outly/intercept/route.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Return the PGL event ID as the evidence ID

For every successful intercept, this replaces the actual pglAnchored.event_id with the caller-controlled action ID. Consumers pass evidence_reference.evidence_id to /api/capi/v1/evidence/[id], which forwards that value to PGL's event lookup, so the returned reference can no longer retrieve the event that was just anchored; the outcome route still correctly returns the PGL event ID.

Useful? React with 👍 / 👎.

entry_hash: pglAnchored.event_hash,
ledger: "dual-pgl-lockerphycer"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not claim a dual anchor when Lockerphycer rejects it

When Lockerphycer is unavailable or returns any non-2xx response, registerAuditRecord() returns false, but that result is ignored and the response still labels the evidence as dual-pgl-lockerphycer. This produces a successful compliance response that asserts an audit copy exists when only the PGL event was stored; either fail closed/check lockerphycerAnchored or report only the ledger that actually accepted the record.

Useful? React with 👍 / 👎.

},
});
} catch (error) {
const status = error instanceof IntegrationUnavailable ? 503 : 502;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Ops capi hardening by reprewindai-dev · Pull Request #46 · reprewindai-dev/cAPI · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.example
Original file line numberDiff line numberDiff line change
Expand Up@@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Leave the optional PGL integration unset in the template

The file instructs developers to copy it to .env.local and 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 👍 / 👎.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Match the BYOS endpoint to the bridge payload

With this configuration, every mcp:// capability posts the JSON-RPC tools/call payload from src/lib/covenant/mcp-bridge.ts to /api/v2/invoke. The checked-in Interlink router binds /api/v2/invoke to an InvocationRequest requiring capability_id, arguments, and context, while its JSON-RPC handler is a different route, so the configured endpoint rejects the bridge payload before execution; configure a compatible MCP endpoint or serialize the REST invocation contract.

Useful? React with 👍 / 👎.

BYOS_INTERNAL_API_KEY=
COVENANT_EXEC_TIMEOUT_MS=10000

Expand Down
38 changes: 38 additions & 0 deletions .github/workflows/mayhem-dast.yml
Original file line numberDiff line numberDiff 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Point Mayhem at the port started by the workflow

In this workflow, npm run dev expands to next dev -p 3003, while Mayhem is configured to connect to port 3000. Next's -p, --port <port> option explicitly selects the listening port, so the CLI argument takes precedence over the step's PORT=3000; after the fixed sleep, the action probes a port with no cAPI server and the new DAST job cannot exercise the API.

Useful? React with 👍 / 👎.

api-spec: openapi.json
duration: 300
4 changes: 4 additions & 0 deletions Mayhemfile
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
version: '1.0'
api:
openapi: openapi.json
target: http://localhost:3000
2 changes: 1 addition & 1 deletion RUNTIME_PATCH.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,7 +93,7 @@ const response = await getEngine().signAndProcess({ ... });
Add to `.env.local` (never commit):

```env
BYOS_MCP_GATEWAY_URL=https://api.veklom.com/api/v1/mcp
BYOS_MCP_GATEWAY_URL=http://n13gp1nhrcdp0hvazvbnlxru-213557155694:8088/api/v1/mcp
BYOS_INTERNAL_API_KEY=your_internal_key_here
COVENANT_EXEC_TIMEOUT_MS=10000
```
Expand Down
49 changes: 49 additions & 0 deletions openapi.json
Original file line numberDiff line numberDiff 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Describe the heartbeat payload accepted by the route

The heartbeat route validates a strict object containing only required service_name, but the new OpenAPI schema advertises service and status and does not require either field. Consequently every request generated from the documented properties is rejected with 400, so Mayhem cannot reach the heartbeat logic and may report the undocumented response instead of fuzzing the intended endpoint.

Useful? React with 👍 / 👎.

}
}
}
}
},
"responses": {
"200": {
"description": "OK"
}
}
}
}
}
}
63 changes: 63 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,9 +3,9 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "next dev -p 3003",
"build": "next build",
"start": "next start",
"start": "next start -p 3003",
"lint": "next lint",
"test": "vitest run"
},
Expand All@@ -19,6 +19,7 @@
"zod": "^4.4.3"
},
"devDependencies": {
"@playwright/test": "^1.62.0",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand Down
Binary file addedpublic/favicon.ico
Binary file not shown.
Binary file addedpublic/og-image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file addedpublic/twitter-card.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions scripts/outly-booking-test.ts
Original file line numberDiff line numberDiff 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", {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the booking test harness off production by default

Running this developer test script always submits its generated demo action to the production capi.veklom.com endpoint, even though its failure message tells the user to start a localhost server. Each invocation can therefore add synthetic Outly decisions to the production immutable PGL and Lockerphycer audit trail; use an environment-provided base URL with a localhost default and require an explicit opt-in for 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();
64 changes: 60 additions & 4 deletions src/app/api/outly/intercept/route.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Return the PGL event ID as the evidence ID

For every successful intercept, this replaces the actual pglAnchored.event_id with the caller-controlled action ID. Consumers pass evidence_reference.evidence_id to /api/capi/v1/evidence/[id], which forwards that value to PGL's event lookup, so the returned reference can no longer retrieve the event that was just anchored; the outcome route still correctly returns the PGL event ID.

Useful? React with 👍 / 👎.

entry_hash: pglAnchored.event_hash,
ledger: "dual-pgl-lockerphycer"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not claim a dual anchor when Lockerphycer rejects it

When Lockerphycer is unavailable or returns any non-2xx response, registerAuditRecord() returns false, but that result is ignored and the response still labels the evidence as dual-pgl-lockerphycer. This produces a successful compliance response that asserts an audit copy exists when only the PGL event was stored; either fail closed/check lockerphycerAnchored or report only the ledger that actually accepted the record.

Useful? React with 👍 / 👎.

},
});
} catch (error) {
const status = error instanceof IntegrationUnavailable ? 503 : 502;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Ops capi hardening by reprewindai-dev · Pull Request #46 · reprewindai-dev/cAPI · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.example
Original file line numberDiff line numberDiff line change
Expand Up@@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Leave the optional PGL integration unset in the template

The file instructs developers to copy it to .env.local and 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 👍 / 👎.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Match the BYOS endpoint to the bridge payload

With this configuration, every mcp:// capability posts the JSON-RPC tools/call payload from src/lib/covenant/mcp-bridge.ts to /api/v2/invoke. The checked-in Interlink router binds /api/v2/invoke to an InvocationRequest requiring capability_id, arguments, and context, while its JSON-RPC handler is a different route, so the configured endpoint rejects the bridge payload before execution; configure a compatible MCP endpoint or serialize the REST invocation contract.

Useful? React with 👍 / 👎.

BYOS_INTERNAL_API_KEY=
COVENANT_EXEC_TIMEOUT_MS=10000

Expand Down
38 changes: 38 additions & 0 deletions .github/workflows/mayhem-dast.yml
Original file line numberDiff line numberDiff 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Point Mayhem at the port started by the workflow

In this workflow, npm run dev expands to next dev -p 3003, while Mayhem is configured to connect to port 3000. Next's -p, --port <port> option explicitly selects the listening port, so the CLI argument takes precedence over the step's PORT=3000; after the fixed sleep, the action probes a port with no cAPI server and the new DAST job cannot exercise the API.

Useful? React with 👍 / 👎.

api-spec: openapi.json
duration: 300
4 changes: 4 additions & 0 deletions Mayhemfile
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
version: '1.0'
api:
openapi: openapi.json
target: http://localhost:3000
2 changes: 1 addition & 1 deletion RUNTIME_PATCH.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,7 +93,7 @@ const response = await getEngine().signAndProcess({ ... });
Add to `.env.local` (never commit):

```env
BYOS_MCP_GATEWAY_URL=https://api.veklom.com/api/v1/mcp
BYOS_MCP_GATEWAY_URL=http://n13gp1nhrcdp0hvazvbnlxru-213557155694:8088/api/v1/mcp
BYOS_INTERNAL_API_KEY=your_internal_key_here
COVENANT_EXEC_TIMEOUT_MS=10000
```
Expand Down
49 changes: 49 additions & 0 deletions openapi.json
Original file line numberDiff line numberDiff 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Describe the heartbeat payload accepted by the route

The heartbeat route validates a strict object containing only required service_name, but the new OpenAPI schema advertises service and status and does not require either field. Consequently every request generated from the documented properties is rejected with 400, so Mayhem cannot reach the heartbeat logic and may report the undocumented response instead of fuzzing the intended endpoint.

Useful? React with 👍 / 👎.

}
}
}
}
},
"responses": {
"200": {
"description": "OK"
}
}
}
}
}
}
63 changes: 63 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,9 +3,9 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "next dev -p 3003",
"build": "next build",
"start": "next start",
"start": "next start -p 3003",
"lint": "next lint",
"test": "vitest run"
},
Expand All@@ -19,6 +19,7 @@
"zod": "^4.4.3"
},
"devDependencies": {
"@playwright/test": "^1.62.0",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand Down
Binary file addedpublic/favicon.ico
Binary file not shown.
Binary file addedpublic/og-image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file addedpublic/twitter-card.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions scripts/outly-booking-test.ts
Original file line numberDiff line numberDiff 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", {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the booking test harness off production by default

Running this developer test script always submits its generated demo action to the production capi.veklom.com endpoint, even though its failure message tells the user to start a localhost server. Each invocation can therefore add synthetic Outly decisions to the production immutable PGL and Lockerphycer audit trail; use an environment-provided base URL with a localhost default and require an explicit opt-in for 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();
64 changes: 60 additions & 4 deletions src/app/api/outly/intercept/route.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Return the PGL event ID as the evidence ID

For every successful intercept, this replaces the actual pglAnchored.event_id with the caller-controlled action ID. Consumers pass evidence_reference.evidence_id to /api/capi/v1/evidence/[id], which forwards that value to PGL's event lookup, so the returned reference can no longer retrieve the event that was just anchored; the outcome route still correctly returns the PGL event ID.

Useful? React with 👍 / 👎.

entry_hash: pglAnchored.event_hash,
ledger: "dual-pgl-lockerphycer"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not claim a dual anchor when Lockerphycer rejects it

When Lockerphycer is unavailable or returns any non-2xx response, registerAuditRecord() returns false, but that result is ignored and the response still labels the evidence as dual-pgl-lockerphycer. This produces a successful compliance response that asserts an audit copy exists when only the PGL event was stored; either fail closed/check lockerphycerAnchored or report only the ledger that actually accepted the record.

Useful? React with 👍 / 👎.

},
});
} catch (error) {
const status = error instanceof IntegrationUnavailable ? 503 : 502;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Ops capi hardening by reprewindai-dev · Pull Request #46 · reprewindai-dev/cAPI · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.example
Original file line numberDiff line numberDiff line change
Expand Up@@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Leave the optional PGL integration unset in the template

The file instructs developers to copy it to .env.local and 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 👍 / 👎.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Match the BYOS endpoint to the bridge payload

With this configuration, every mcp:// capability posts the JSON-RPC tools/call payload from src/lib/covenant/mcp-bridge.ts to /api/v2/invoke. The checked-in Interlink router binds /api/v2/invoke to an InvocationRequest requiring capability_id, arguments, and context, while its JSON-RPC handler is a different route, so the configured endpoint rejects the bridge payload before execution; configure a compatible MCP endpoint or serialize the REST invocation contract.

Useful? React with 👍 / 👎.

BYOS_INTERNAL_API_KEY=
COVENANT_EXEC_TIMEOUT_MS=10000

Expand Down
38 changes: 38 additions & 0 deletions .github/workflows/mayhem-dast.yml
Original file line numberDiff line numberDiff 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Point Mayhem at the port started by the workflow

In this workflow, npm run dev expands to next dev -p 3003, while Mayhem is configured to connect to port 3000. Next's -p, --port <port> option explicitly selects the listening port, so the CLI argument takes precedence over the step's PORT=3000; after the fixed sleep, the action probes a port with no cAPI server and the new DAST job cannot exercise the API.

Useful? React with 👍 / 👎.

api-spec: openapi.json
duration: 300
4 changes: 4 additions & 0 deletions Mayhemfile
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
version: '1.0'
api:
openapi: openapi.json
target: http://localhost:3000
2 changes: 1 addition & 1 deletion RUNTIME_PATCH.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,7 +93,7 @@ const response = await getEngine().signAndProcess({ ... });
Add to `.env.local` (never commit):

```env
BYOS_MCP_GATEWAY_URL=https://api.veklom.com/api/v1/mcp
BYOS_MCP_GATEWAY_URL=http://n13gp1nhrcdp0hvazvbnlxru-213557155694:8088/api/v1/mcp
BYOS_INTERNAL_API_KEY=your_internal_key_here
COVENANT_EXEC_TIMEOUT_MS=10000
```
Expand Down
49 changes: 49 additions & 0 deletions openapi.json
Original file line numberDiff line numberDiff 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Describe the heartbeat payload accepted by the route

The heartbeat route validates a strict object containing only required service_name, but the new OpenAPI schema advertises service and status and does not require either field. Consequently every request generated from the documented properties is rejected with 400, so Mayhem cannot reach the heartbeat logic and may report the undocumented response instead of fuzzing the intended endpoint.

Useful? React with 👍 / 👎.

}
}
}
}
},
"responses": {
"200": {
"description": "OK"
}
}
}
}
}
}
63 changes: 63 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,9 +3,9 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "next dev -p 3003",
"build": "next build",
"start": "next start",
"start": "next start -p 3003",
"lint": "next lint",
"test": "vitest run"
},
Expand All@@ -19,6 +19,7 @@
"zod": "^4.4.3"
},
"devDependencies": {
"@playwright/test": "^1.62.0",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand Down
Binary file addedpublic/favicon.ico
Binary file not shown.
Binary file addedpublic/og-image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file addedpublic/twitter-card.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions scripts/outly-booking-test.ts
Original file line numberDiff line numberDiff 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", {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the booking test harness off production by default

Running this developer test script always submits its generated demo action to the production capi.veklom.com endpoint, even though its failure message tells the user to start a localhost server. Each invocation can therefore add synthetic Outly decisions to the production immutable PGL and Lockerphycer audit trail; use an environment-provided base URL with a localhost default and require an explicit opt-in for 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();
64 changes: 60 additions & 4 deletions src/app/api/outly/intercept/route.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Return the PGL event ID as the evidence ID

For every successful intercept, this replaces the actual pglAnchored.event_id with the caller-controlled action ID. Consumers pass evidence_reference.evidence_id to /api/capi/v1/evidence/[id], which forwards that value to PGL's event lookup, so the returned reference can no longer retrieve the event that was just anchored; the outcome route still correctly returns the PGL event ID.

Useful? React with 👍 / 👎.

entry_hash: pglAnchored.event_hash,
ledger: "dual-pgl-lockerphycer"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not claim a dual anchor when Lockerphycer rejects it

When Lockerphycer is unavailable or returns any non-2xx response, registerAuditRecord() returns false, but that result is ignored and the response still labels the evidence as dual-pgl-lockerphycer. This produces a successful compliance response that asserts an audit copy exists when only the PGL event was stored; either fail closed/check lockerphycerAnchored or report only the ledger that actually accepted the record.

Useful? React with 👍 / 👎.

},
});
} catch (error) {
const status = error instanceof IntegrationUnavailable ? 503 : 502;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Ops capi hardening by reprewindai-dev · Pull Request #46 · reprewindai-dev/cAPI · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.example
Original file line numberDiff line numberDiff line change
Expand Up@@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Leave the optional PGL integration unset in the template

The file instructs developers to copy it to .env.local and 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 👍 / 👎.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Match the BYOS endpoint to the bridge payload

With this configuration, every mcp:// capability posts the JSON-RPC tools/call payload from src/lib/covenant/mcp-bridge.ts to /api/v2/invoke. The checked-in Interlink router binds /api/v2/invoke to an InvocationRequest requiring capability_id, arguments, and context, while its JSON-RPC handler is a different route, so the configured endpoint rejects the bridge payload before execution; configure a compatible MCP endpoint or serialize the REST invocation contract.

Useful? React with 👍 / 👎.

BYOS_INTERNAL_API_KEY=
COVENANT_EXEC_TIMEOUT_MS=10000

Expand Down
38 changes: 38 additions & 0 deletions .github/workflows/mayhem-dast.yml
Original file line numberDiff line numberDiff 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Point Mayhem at the port started by the workflow

In this workflow, npm run dev expands to next dev -p 3003, while Mayhem is configured to connect to port 3000. Next's -p, --port <port> option explicitly selects the listening port, so the CLI argument takes precedence over the step's PORT=3000; after the fixed sleep, the action probes a port with no cAPI server and the new DAST job cannot exercise the API.

Useful? React with 👍 / 👎.

api-spec: openapi.json
duration: 300
4 changes: 4 additions & 0 deletions Mayhemfile
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
version: '1.0'
api:
openapi: openapi.json
target: http://localhost:3000
2 changes: 1 addition & 1 deletion RUNTIME_PATCH.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,7 +93,7 @@ const response = await getEngine().signAndProcess({ ... });
Add to `.env.local` (never commit):

```env
BYOS_MCP_GATEWAY_URL=https://api.veklom.com/api/v1/mcp
BYOS_MCP_GATEWAY_URL=http://n13gp1nhrcdp0hvazvbnlxru-213557155694:8088/api/v1/mcp
BYOS_INTERNAL_API_KEY=your_internal_key_here
COVENANT_EXEC_TIMEOUT_MS=10000
```
Expand Down
49 changes: 49 additions & 0 deletions openapi.json
Original file line numberDiff line numberDiff 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Describe the heartbeat payload accepted by the route

The heartbeat route validates a strict object containing only required service_name, but the new OpenAPI schema advertises service and status and does not require either field. Consequently every request generated from the documented properties is rejected with 400, so Mayhem cannot reach the heartbeat logic and may report the undocumented response instead of fuzzing the intended endpoint.

Useful? React with 👍 / 👎.

}
}
}
}
},
"responses": {
"200": {
"description": "OK"
}
}
}
}
}
}
63 changes: 63 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,9 +3,9 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "next dev -p 3003",
"build": "next build",
"start": "next start",
"start": "next start -p 3003",
"lint": "next lint",
"test": "vitest run"
},
Expand All@@ -19,6 +19,7 @@
"zod": "^4.4.3"
},
"devDependencies": {
"@playwright/test": "^1.62.0",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand Down
Binary file addedpublic/favicon.ico
Binary file not shown.
Binary file addedpublic/og-image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file addedpublic/twitter-card.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions scripts/outly-booking-test.ts
Original file line numberDiff line numberDiff 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", {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the booking test harness off production by default

Running this developer test script always submits its generated demo action to the production capi.veklom.com endpoint, even though its failure message tells the user to start a localhost server. Each invocation can therefore add synthetic Outly decisions to the production immutable PGL and Lockerphycer audit trail; use an environment-provided base URL with a localhost default and require an explicit opt-in for 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();
64 changes: 60 additions & 4 deletions src/app/api/outly/intercept/route.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Return the PGL event ID as the evidence ID

For every successful intercept, this replaces the actual pglAnchored.event_id with the caller-controlled action ID. Consumers pass evidence_reference.evidence_id to /api/capi/v1/evidence/[id], which forwards that value to PGL's event lookup, so the returned reference can no longer retrieve the event that was just anchored; the outcome route still correctly returns the PGL event ID.

Useful? React with 👍 / 👎.

entry_hash: pglAnchored.event_hash,
ledger: "dual-pgl-lockerphycer"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not claim a dual anchor when Lockerphycer rejects it

When Lockerphycer is unavailable or returns any non-2xx response, registerAuditRecord() returns false, but that result is ignored and the response still labels the evidence as dual-pgl-lockerphycer. This produces a successful compliance response that asserts an audit copy exists when only the PGL event was stored; either fail closed/check lockerphycerAnchored or report only the ledger that actually accepted the record.

Useful? React with 👍 / 👎.

},
});
} catch (error) {
const status = error instanceof IntegrationUnavailable ? 503 : 502;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Ops capi hardening by reprewindai-dev · Pull Request #46 · reprewindai-dev/cAPI · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.example
Original file line numberDiff line numberDiff line change
Expand Up@@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Leave the optional PGL integration unset in the template

The file instructs developers to copy it to .env.local and 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 👍 / 👎.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Match the BYOS endpoint to the bridge payload

With this configuration, every mcp:// capability posts the JSON-RPC tools/call payload from src/lib/covenant/mcp-bridge.ts to /api/v2/invoke. The checked-in Interlink router binds /api/v2/invoke to an InvocationRequest requiring capability_id, arguments, and context, while its JSON-RPC handler is a different route, so the configured endpoint rejects the bridge payload before execution; configure a compatible MCP endpoint or serialize the REST invocation contract.

Useful? React with 👍 / 👎.

BYOS_INTERNAL_API_KEY=
COVENANT_EXEC_TIMEOUT_MS=10000

Expand Down
38 changes: 38 additions & 0 deletions .github/workflows/mayhem-dast.yml
Original file line numberDiff line numberDiff 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Point Mayhem at the port started by the workflow

In this workflow, npm run dev expands to next dev -p 3003, while Mayhem is configured to connect to port 3000. Next's -p, --port <port> option explicitly selects the listening port, so the CLI argument takes precedence over the step's PORT=3000; after the fixed sleep, the action probes a port with no cAPI server and the new DAST job cannot exercise the API.

Useful? React with 👍 / 👎.

api-spec: openapi.json
duration: 300
4 changes: 4 additions & 0 deletions Mayhemfile
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
version: '1.0'
api:
openapi: openapi.json
target: http://localhost:3000
2 changes: 1 addition & 1 deletion RUNTIME_PATCH.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,7 +93,7 @@ const response = await getEngine().signAndProcess({ ... });
Add to `.env.local` (never commit):

```env
BYOS_MCP_GATEWAY_URL=https://api.veklom.com/api/v1/mcp
BYOS_MCP_GATEWAY_URL=http://n13gp1nhrcdp0hvazvbnlxru-213557155694:8088/api/v1/mcp
BYOS_INTERNAL_API_KEY=your_internal_key_here
COVENANT_EXEC_TIMEOUT_MS=10000
```
Expand Down
49 changes: 49 additions & 0 deletions openapi.json
Original file line numberDiff line numberDiff 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Describe the heartbeat payload accepted by the route

The heartbeat route validates a strict object containing only required service_name, but the new OpenAPI schema advertises service and status and does not require either field. Consequently every request generated from the documented properties is rejected with 400, so Mayhem cannot reach the heartbeat logic and may report the undocumented response instead of fuzzing the intended endpoint.

Useful? React with 👍 / 👎.

}
}
}
}
},
"responses": {
"200": {
"description": "OK"
}
}
}
}
}
}
63 changes: 63 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,9 +3,9 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "next dev -p 3003",
"build": "next build",
"start": "next start",
"start": "next start -p 3003",
"lint": "next lint",
"test": "vitest run"
},
Expand All@@ -19,6 +19,7 @@
"zod": "^4.4.3"
},
"devDependencies": {
"@playwright/test": "^1.62.0",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand Down
Binary file addedpublic/favicon.ico
Binary file not shown.
Binary file addedpublic/og-image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file addedpublic/twitter-card.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions scripts/outly-booking-test.ts
Original file line numberDiff line numberDiff 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", {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the booking test harness off production by default

Running this developer test script always submits its generated demo action to the production capi.veklom.com endpoint, even though its failure message tells the user to start a localhost server. Each invocation can therefore add synthetic Outly decisions to the production immutable PGL and Lockerphycer audit trail; use an environment-provided base URL with a localhost default and require an explicit opt-in for 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();
64 changes: 60 additions & 4 deletions src/app/api/outly/intercept/route.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Return the PGL event ID as the evidence ID

For every successful intercept, this replaces the actual pglAnchored.event_id with the caller-controlled action ID. Consumers pass evidence_reference.evidence_id to /api/capi/v1/evidence/[id], which forwards that value to PGL's event lookup, so the returned reference can no longer retrieve the event that was just anchored; the outcome route still correctly returns the PGL event ID.

Useful? React with 👍 / 👎.

entry_hash: pglAnchored.event_hash,
ledger: "dual-pgl-lockerphycer"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not claim a dual anchor when Lockerphycer rejects it

When Lockerphycer is unavailable or returns any non-2xx response, registerAuditRecord() returns false, but that result is ignored and the response still labels the evidence as dual-pgl-lockerphycer. This produces a successful compliance response that asserts an audit copy exists when only the PGL event was stored; either fail closed/check lockerphycerAnchored or report only the ledger that actually accepted the record.

Useful? React with 👍 / 👎.

},
});
} catch (error) {
const status = error instanceof IntegrationUnavailable ? 503 : 502;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Ops capi hardening by reprewindai-dev · Pull Request #46 · reprewindai-dev/cAPI · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.example
Original file line numberDiff line numberDiff line change
Expand Up@@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Leave the optional PGL integration unset in the template

The file instructs developers to copy it to .env.local and 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 👍 / 👎.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Match the BYOS endpoint to the bridge payload

With this configuration, every mcp:// capability posts the JSON-RPC tools/call payload from src/lib/covenant/mcp-bridge.ts to /api/v2/invoke. The checked-in Interlink router binds /api/v2/invoke to an InvocationRequest requiring capability_id, arguments, and context, while its JSON-RPC handler is a different route, so the configured endpoint rejects the bridge payload before execution; configure a compatible MCP endpoint or serialize the REST invocation contract.

Useful? React with 👍 / 👎.

BYOS_INTERNAL_API_KEY=
COVENANT_EXEC_TIMEOUT_MS=10000

Expand Down
38 changes: 38 additions & 0 deletions .github/workflows/mayhem-dast.yml
Original file line numberDiff line numberDiff 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Point Mayhem at the port started by the workflow

In this workflow, npm run dev expands to next dev -p 3003, while Mayhem is configured to connect to port 3000. Next's -p, --port <port> option explicitly selects the listening port, so the CLI argument takes precedence over the step's PORT=3000; after the fixed sleep, the action probes a port with no cAPI server and the new DAST job cannot exercise the API.

Useful? React with 👍 / 👎.

api-spec: openapi.json
duration: 300
4 changes: 4 additions & 0 deletions Mayhemfile
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
version: '1.0'
api:
openapi: openapi.json
target: http://localhost:3000
2 changes: 1 addition & 1 deletion RUNTIME_PATCH.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,7 +93,7 @@ const response = await getEngine().signAndProcess({ ... });
Add to `.env.local` (never commit):

```env
BYOS_MCP_GATEWAY_URL=https://api.veklom.com/api/v1/mcp
BYOS_MCP_GATEWAY_URL=http://n13gp1nhrcdp0hvazvbnlxru-213557155694:8088/api/v1/mcp
BYOS_INTERNAL_API_KEY=your_internal_key_here
COVENANT_EXEC_TIMEOUT_MS=10000
```
Expand Down
49 changes: 49 additions & 0 deletions openapi.json
Original file line numberDiff line numberDiff 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Describe the heartbeat payload accepted by the route

The heartbeat route validates a strict object containing only required service_name, but the new OpenAPI schema advertises service and status and does not require either field. Consequently every request generated from the documented properties is rejected with 400, so Mayhem cannot reach the heartbeat logic and may report the undocumented response instead of fuzzing the intended endpoint.

Useful? React with 👍 / 👎.

}
}
}
}
},
"responses": {
"200": {
"description": "OK"
}
}
}
}
}
}
63 changes: 63 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,9 +3,9 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "next dev -p 3003",
"build": "next build",
"start": "next start",
"start": "next start -p 3003",
"lint": "next lint",
"test": "vitest run"
},
Expand All@@ -19,6 +19,7 @@
"zod": "^4.4.3"
},
"devDependencies": {
"@playwright/test": "^1.62.0",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand Down
Binary file addedpublic/favicon.ico
Binary file not shown.
Binary file addedpublic/og-image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file addedpublic/twitter-card.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions scripts/outly-booking-test.ts
Original file line numberDiff line numberDiff 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", {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the booking test harness off production by default

Running this developer test script always submits its generated demo action to the production capi.veklom.com endpoint, even though its failure message tells the user to start a localhost server. Each invocation can therefore add synthetic Outly decisions to the production immutable PGL and Lockerphycer audit trail; use an environment-provided base URL with a localhost default and require an explicit opt-in for 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();
64 changes: 60 additions & 4 deletions src/app/api/outly/intercept/route.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Return the PGL event ID as the evidence ID

For every successful intercept, this replaces the actual pglAnchored.event_id with the caller-controlled action ID. Consumers pass evidence_reference.evidence_id to /api/capi/v1/evidence/[id], which forwards that value to PGL's event lookup, so the returned reference can no longer retrieve the event that was just anchored; the outcome route still correctly returns the PGL event ID.

Useful? React with 👍 / 👎.

entry_hash: pglAnchored.event_hash,
ledger: "dual-pgl-lockerphycer"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not claim a dual anchor when Lockerphycer rejects it

When Lockerphycer is unavailable or returns any non-2xx response, registerAuditRecord() returns false, but that result is ignored and the response still labels the evidence as dual-pgl-lockerphycer. This produces a successful compliance response that asserts an audit copy exists when only the PGL event was stored; either fail closed/check lockerphycerAnchored or report only the ledger that actually accepted the record.

Useful? React with 👍 / 👎.

},
});
} catch (error) {
const status = error instanceof IntegrationUnavailable ? 503 : 502;
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Ops capi hardening by reprewindai-dev · Pull Request #46 · reprewindai-dev/cAPI · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.example
Original file line numberDiff line numberDiff line change
Expand Up@@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Leave the optional PGL integration unset in the template

The file instructs developers to copy it to .env.local and 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 👍 / 👎.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Match the BYOS endpoint to the bridge payload

With this configuration, every mcp:// capability posts the JSON-RPC tools/call payload from src/lib/covenant/mcp-bridge.ts to /api/v2/invoke. The checked-in Interlink router binds /api/v2/invoke to an InvocationRequest requiring capability_id, arguments, and context, while its JSON-RPC handler is a different route, so the configured endpoint rejects the bridge payload before execution; configure a compatible MCP endpoint or serialize the REST invocation contract.

Useful? React with 👍 / 👎.

BYOS_INTERNAL_API_KEY=
COVENANT_EXEC_TIMEOUT_MS=10000

Expand Down
38 changes: 38 additions & 0 deletions .github/workflows/mayhem-dast.yml
Original file line numberDiff line numberDiff 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Point Mayhem at the port started by the workflow

In this workflow, npm run dev expands to next dev -p 3003, while Mayhem is configured to connect to port 3000. Next's -p, --port <port> option explicitly selects the listening port, so the CLI argument takes precedence over the step's PORT=3000; after the fixed sleep, the action probes a port with no cAPI server and the new DAST job cannot exercise the API.

Useful? React with 👍 / 👎.

api-spec: openapi.json
duration: 300
4 changes: 4 additions & 0 deletions Mayhemfile
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
version: '1.0'
api:
openapi: openapi.json
target: http://localhost:3000
2 changes: 1 addition & 1 deletion RUNTIME_PATCH.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,7 +93,7 @@ const response = await getEngine().signAndProcess({ ... });
Add to `.env.local` (never commit):

```env
BYOS_MCP_GATEWAY_URL=https://api.veklom.com/api/v1/mcp
BYOS_MCP_GATEWAY_URL=http://n13gp1nhrcdp0hvazvbnlxru-213557155694:8088/api/v1/mcp
BYOS_INTERNAL_API_KEY=your_internal_key_here
COVENANT_EXEC_TIMEOUT_MS=10000
```
Expand Down
49 changes: 49 additions & 0 deletions openapi.json
Original file line numberDiff line numberDiff 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Describe the heartbeat payload accepted by the route

The heartbeat route validates a strict object containing only required service_name, but the new OpenAPI schema advertises service and status and does not require either field. Consequently every request generated from the documented properties is rejected with 400, so Mayhem cannot reach the heartbeat logic and may report the undocumented response instead of fuzzing the intended endpoint.

Useful? React with 👍 / 👎.

}
}
}
}
},
"responses": {
"200": {
"description": "OK"
}
}
}
}
}
}
63 changes: 63 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,9 +3,9 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "next dev -p 3003",
"build": "next build",
"start": "next start",
"start": "next start -p 3003",
"lint": "next lint",
"test": "vitest run"
},
Expand All@@ -19,6 +19,7 @@
"zod": "^4.4.3"
},
"devDependencies": {
"@playwright/test": "^1.62.0",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand Down
Binary file addedpublic/favicon.ico
Binary file not shown.
Binary file addedpublic/og-image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file addedpublic/twitter-card.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions scripts/outly-booking-test.ts
Original file line numberDiff line numberDiff 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", {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the booking test harness off production by default

Running this developer test script always submits its generated demo action to the production capi.veklom.com endpoint, even though its failure message tells the user to start a localhost server. Each invocation can therefore add synthetic Outly decisions to the production immutable PGL and Lockerphycer audit trail; use an environment-provided base URL with a localhost default and require an explicit opt-in for 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();
64 changes: 60 additions & 4 deletions src/app/api/outly/intercept/route.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Return the PGL event ID as the evidence ID

For every successful intercept, this replaces the actual pglAnchored.event_id with the caller-controlled action ID. Consumers pass evidence_reference.evidence_id to /api/capi/v1/evidence/[id], which forwards that value to PGL's event lookup, so the returned reference can no longer retrieve the event that was just anchored; the outcome route still correctly returns the PGL event ID.

Useful? React with 👍 / 👎.

entry_hash: pglAnchored.event_hash,
ledger: "dual-pgl-lockerphycer"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not claim a dual anchor when Lockerphycer rejects it

When Lockerphycer is unavailable or returns any non-2xx response, registerAuditRecord() returns false, but that result is ignored and the response still labels the evidence as dual-pgl-lockerphycer. This produces a successful compliance response that asserts an audit copy exists when only the PGL event was stored; either fail closed/check lockerphycerAnchored or report only the ledger that actually accepted the record.

Useful? React with 👍 / 👎.

},
});
} catch (error) {
const status = error instanceof IntegrationUnavailable ? 503 : 502;
Expand Down
Loading