@yiaany/ghostapi 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/CONTRIBUTING.md +132 -119
- package/LICENSE +21 -21
- package/PROVENANCE.md +13 -0
- package/README.md +78 -80
- package/SECURITY.md +56 -53
- package/dist/actions/gateway.js +320 -72
- package/dist/actions/gateway.js.map +1 -1
- package/dist/actions/index.d.ts +2 -2
- package/dist/actions/index.js +1 -1
- package/dist/actions/index.js.map +1 -1
- package/dist/agents/agentPrompt.js +7 -5
- package/dist/agents/agentPrompt.js.map +1 -1
- package/dist/ai/aiClient.js +5 -5
- package/dist/ai/aiClient.js.map +1 -1
- package/dist/ai/aiGenerator.js +66 -26
- package/dist/ai/aiGenerator.js.map +1 -1
- package/dist/ai/genericInference.js +151 -36
- package/dist/ai/genericInference.js.map +1 -1
- package/dist/ai/prompts.js +55 -49
- package/dist/ai/prompts.js.map +1 -1
- package/dist/approvals/approvalInbox.js +649 -133
- package/dist/approvals/approvalInbox.js.map +1 -1
- package/dist/approvals/index.d.ts +2 -2
- package/dist/approvals/index.js +1 -1
- package/dist/approvals/index.js.map +1 -1
- package/dist/behavior/behaviorStore.js +34 -14
- package/dist/behavior/behaviorStore.js.map +1 -1
- package/dist/cache/index.js +14 -5
- package/dist/cache/index.js.map +1 -1
- package/dist/cli/index.js +226 -93
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/openUrl.js +17 -3
- package/dist/cli/openUrl.js.map +1 -1
- package/dist/cli/parser.js +79 -18
- package/dist/cli/parser.js.map +1 -1
- package/dist/config/dataPaths.js +4 -2
- package/dist/config/dataPaths.js.map +1 -1
- package/dist/config/localConfig.js +5 -2
- package/dist/config/localConfig.js.map +1 -1
- package/dist/config/serverConfig.js +11 -5
- package/dist/config/serverConfig.js.map +1 -1
- package/dist/contracts/contract.js +311 -83
- package/dist/contracts/contract.js.map +1 -1
- package/dist/contracts/index.d.ts +2 -2
- package/dist/contracts/index.js +1 -1
- package/dist/contracts/index.js.map +1 -1
- package/dist/credentials/credentialBroker.js +391 -79
- package/dist/credentials/credentialBroker.js.map +1 -1
- package/dist/credentials/index.d.ts +2 -2
- package/dist/credentials/index.js +1 -1
- package/dist/credentials/index.js.map +1 -1
- package/dist/dashboard/app.js +823 -685
- package/dist/dashboard/dashboard.js.map +1 -1
- package/dist/dashboard/index.html +704 -267
- package/dist/dashboard/styles.css +1040 -349
- package/dist/egress/capabilities.js +55 -25
- package/dist/egress/capabilities.js.map +1 -1
- package/dist/egress/linuxBootstrap.js +47 -11
- package/dist/egress/linuxBootstrap.js.map +1 -1
- package/dist/egress/run.js +170 -31
- package/dist/egress/run.js.map +1 -1
- package/dist/errors/errorEngine.js +15 -3
- package/dist/errors/errorEngine.js.map +1 -1
- package/dist/errors/providerErrors.js.map +1 -1
- package/dist/evals/evals.js +304 -78
- package/dist/evals/evals.js.map +1 -1
- package/dist/evals/index.d.ts +2 -2
- package/dist/evals/index.js +1 -1
- package/dist/evals/index.js.map +1 -1
- package/dist/evidence/index.d.ts +2 -2
- package/dist/evidence/index.js +1 -1
- package/dist/evidence/index.js.map +1 -1
- package/dist/evidence/report.d.ts +1 -1
- package/dist/evidence/report.js +263 -79
- package/dist/evidence/report.js.map +1 -1
- package/dist/fault/faultLab.js +24 -9
- package/dist/fault/faultLab.js.map +1 -1
- package/dist/index.d.ts +42 -42
- package/dist/index.js +21 -21
- package/dist/index.js.map +1 -1
- package/dist/inventory/analysis.d.ts +1 -1
- package/dist/inventory/analysis.js +174 -50
- package/dist/inventory/analysis.js.map +1 -1
- package/dist/inventory/index.d.ts +4 -4
- package/dist/inventory/index.js +3 -3
- package/dist/inventory/index.js.map +1 -1
- package/dist/inventory/inventoryController.js +362 -115
- package/dist/inventory/inventoryController.js.map +1 -1
- package/dist/inventory/types.js +680 -140
- package/dist/inventory/types.js.map +1 -1
- package/dist/ledger/actionLedger.js +355 -78
- package/dist/ledger/actionLedger.js.map +1 -1
- package/dist/ledger/index.d.ts +2 -2
- package/dist/ledger/index.js +1 -1
- package/dist/ledger/index.js.map +1 -1
- package/dist/mcp/server.js +26 -11
- package/dist/mcp/server.js.map +1 -1
- package/dist/policy/index.d.ts +2 -2
- package/dist/policy/index.js +1 -1
- package/dist/policy/index.js.map +1 -1
- package/dist/policy/policy.js +112 -32
- package/dist/policy/policy.js.map +1 -1
- package/dist/productTelemetry/index.js +71 -20
- package/dist/productTelemetry/index.js.map +1 -1
- package/dist/providers/conformance.js +10 -5
- package/dist/providers/conformance.js.map +1 -1
- package/dist/providers/discord.js +2 -2
- package/dist/providers/generic.js +3 -3
- package/dist/providers/github.js +2 -2
- package/dist/providers/index.d.ts +3 -3
- package/dist/providers/index.js +2 -2
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/openai.js +3 -3
- package/dist/providers/packs/resendPack.js +51 -25
- package/dist/providers/packs/resendPack.js.map +1 -1
- package/dist/providers/packs/stripePack.js +590 -121
- package/dist/providers/packs/stripePack.js.map +1 -1
- package/dist/providers/registry.js +4 -4
- package/dist/providers/registry.js.map +1 -1
- package/dist/providers/runtime.js +7 -6
- package/dist/providers/runtime.js.map +1 -1
- package/dist/providers/stripeWebhook.js +39 -10
- package/dist/providers/stripeWebhook.js.map +1 -1
- package/dist/providers/twilio.js +2 -2
- package/dist/proxy/cacheKey.js +9 -2
- package/dist/proxy/cacheKey.js.map +1 -1
- package/dist/proxy/providerDetector.js +22 -9
- package/dist/proxy/providerDetector.js.map +1 -1
- package/dist/proxy/proxyHandler.js +57 -22
- package/dist/proxy/proxyHandler.js.map +1 -1
- package/dist/proxy/requestNormalizer.js +4 -2
- package/dist/proxy/requestNormalizer.js.map +1 -1
- package/dist/reliability/costGovernance.js +211 -49
- package/dist/reliability/costGovernance.js.map +1 -1
- package/dist/reliability/index.d.ts +8 -8
- package/dist/reliability/index.js +4 -4
- package/dist/reliability/index.js.map +1 -1
- package/dist/reliability/reconciliation.js +274 -65
- package/dist/reliability/reconciliation.js.map +1 -1
- package/dist/reliability/runtimeHealth.js +127 -35
- package/dist/reliability/runtimeHealth.js.map +1 -1
- package/dist/reliability/slo.js +257 -50
- package/dist/reliability/slo.js.map +1 -1
- package/dist/report/safetyReport.d.ts +6 -0
- package/dist/report/safetyReport.js +56 -12
- package/dist/report/safetyReport.js.map +1 -1
- package/dist/rules/aiRules.js +5 -5
- package/dist/rules/aiRules.js.map +1 -1
- package/dist/safety/index.d.ts +2 -2
- package/dist/safety/index.js +1 -1
- package/dist/safety/index.js.map +1 -1
- package/dist/safety/safetyController.js +675 -165
- package/dist/safety/safetyController.js.map +1 -1
- package/dist/scenarios/scenarioBundle.js +204 -71
- package/dist/scenarios/scenarioBundle.js.map +1 -1
- package/dist/scenarios/scenarioStore.js +88 -29
- package/dist/scenarios/scenarioStore.js.map +1 -1
- package/dist/security/headerSanitizer.js +52 -10
- package/dist/security/headerSanitizer.js.map +1 -1
- package/dist/security/secrets.js +10 -5
- package/dist/security/secrets.js.map +1 -1
- package/dist/server/accessControl.js +37 -10
- package/dist/server/accessControl.js.map +1 -1
- package/dist/server/createServer.js +1 -1
- package/dist/server/createServer.js.map +1 -1
- package/dist/server/eventsStore.js +15 -5
- package/dist/server/eventsStore.js.map +1 -1
- package/dist/server/routes.js +67 -19
- package/dist/server/routes.js.map +1 -1
- package/dist/server/sse.js +1 -1
- package/dist/server/sse.js.map +1 -1
- package/dist/setup/sdkDetector.js +7 -2
- package/dist/setup/sdkDetector.js.map +1 -1
- package/dist/setup/setupGenerator.js +136 -48
- package/dist/setup/setupGenerator.js.map +1 -1
- package/dist/state/stateExtractor.js +3 -1
- package/dist/state/stateExtractor.js.map +1 -1
- package/dist/state/stateResolver.js +10 -7
- package/dist/state/stateResolver.js.map +1 -1
- package/dist/state/stateStore.js +13 -4
- package/dist/state/stateStore.js.map +1 -1
- package/dist/storage/fileStore.js +12 -4
- package/dist/storage/fileStore.js.map +1 -1
- package/dist/teamControl/controlPlane.js +846 -174
- package/dist/teamControl/controlPlane.js.map +1 -1
- package/dist/teamControl/deployment.js +19 -5
- package/dist/teamControl/deployment.js.map +1 -1
- package/dist/teamControl/index.d.ts +4 -4
- package/dist/teamControl/index.js +2 -2
- package/dist/teamControl/index.js.map +1 -1
- package/dist/tests/testGenerator.js +15 -7
- package/dist/tests/testGenerator.js.map +1 -1
- package/dist/trust/index.d.ts +2 -2
- package/dist/trust/index.js +1 -1
- package/dist/trust/index.js.map +1 -1
- package/dist/trust/trustLadder.js +309 -63
- package/dist/trust/trustLadder.js.map +1 -1
- package/dist/worlds/index.d.ts +2 -2
- package/dist/worlds/index.js +1 -1
- package/dist/worlds/index.js.map +1 -1
- package/dist/worlds/worlds.js +364 -65
- package/dist/worlds/worlds.js.map +1 -1
- package/docs/assets/dashboard.png +0 -0
- package/docs/assets/ghostapi-avatar.png +0 -0
- package/docs/ci.md +49 -49
- package/docs/github-actions.md +84 -84
- package/docs/mcp.md +58 -52
- package/docs/policy.md +63 -61
- package/docs/providers/authoring-packs.md +180 -175
- package/docs/providers/stripe-core-pack.md +84 -82
- package/docs/releases/README.md +13 -0
- package/docs/security/action-gateway-threat-model.md +34 -34
- package/docs/security/action-ledger-incident-replay-threat-model.md +42 -42
- package/docs/security/approval-inbox-threat-model.md +35 -35
- package/docs/security/credential-broker-threat-model.md +52 -52
- package/docs/security/egress-threat-model.md +126 -126
- package/docs/security/inventory-threat-model.md +104 -104
- package/docs/security/kill-switch-budgets-threat-model.md +37 -37
- package/docs/security/reliability-threat-model.md +111 -111
- package/docs/security/trust-ladder-threat-model.md +33 -33
- package/docs/telemetry.md +14 -0
- package/docs/usage.md +680 -571
- package/examples/README.md +10 -10
- package/examples/agent-instructions/README.md +39 -39
- package/examples/ci-smoke/README.md +21 -21
- package/examples/ci-smoke/ghostapi.policy.yaml +22 -22
- package/examples/ci-smoke/package.json +9 -9
- package/examples/ci-smoke/production-egress.mjs +4 -2
- package/examples/ci-smoke/safe.mjs +21 -17
- package/examples/evals/README.md +24 -24
- package/examples/evals/retry-after.eval.json +71 -52
- package/examples/generic-rest/README.md +27 -27
- package/examples/github-fetch/README.md +38 -35
- package/examples/openai-streaming/README.md +16 -16
- package/examples/openai-streaming/streaming-tool-call.mjs +44 -31
- package/examples/policy/ghostapi.policy.yaml +27 -27
- package/examples/record-replay/README.md +19 -19
- package/examples/record-replay/replay-requests.json +22 -22
- package/examples/record-replay/stripe-checkout.har.json +30 -30
- package/examples/resend-node/README.md +22 -22
- package/examples/stripe-node/README.md +35 -35
- package/examples/stripe-node/checkout-flow.mjs +87 -61
- package/examples/twilio-node/README.md +21 -21
- package/examples/worlds/README.md +19 -19
- package/examples/worlds/subscription-recovery.mjs +21 -12
- package/package.json +28 -6
- package/dist/landing/assets/index-BXR41fAD.css +0 -1
- package/dist/landing/assets/index-CxWsopzP.css +0 -1
- package/dist/landing/assets/index-D8_ggMRE.js +0 -55
- package/dist/landing/assets/index-DZlpyhqd.js +0 -55
- package/dist/landing/assets/index-Dk-Xezs3.js +0 -55
- package/dist/landing/index.html +0 -15
- package/dist/landing/landing.d.ts +0 -3
- package/dist/landing/landing.js +0 -12
- package/dist/landing/landing.js.map +0 -1
- package/docs/commercial/README.md +0 -29
- package/docs/commercial/data-inventory.md +0 -36
- package/docs/commercial/manual-invoicing.md +0 -44
- package/docs/commercial/metrics.md +0 -51
- package/docs/commercial/pricing.md +0 -103
- package/docs/commercial/privacy-policy-draft.md +0 -41
- package/docs/commercial/terms-topics-for-counsel.md +0 -51
- package/docs/design-partners/README.md +0 -52
- package/docs/design-partners/demo-narratives.md +0 -33
- package/docs/design-partners/design-partner-offer.md +0 -39
- package/docs/design-partners/discovery-questions.md +0 -28
- package/docs/design-partners/feedback-capture-template.md +0 -48
- package/docs/design-partners/icp-one-pager.md +0 -43
- package/docs/design-partners/onboarding-checklist.md +0 -33
- package/docs/design-partners/pilot-success-criteria.md +0 -27
- package/docs/design-partners/pricing-interview-script.md +0 -18
- package/docs/design-partners/telemetry-plan.md +0 -48
- package/docs/development/baseline.md +0 -114
- package/docs/development/onboarding-smoke.md +0 -37
- package/docs/development/verification-0.1.8.md +0 -25
- package/docs/enterprise-product-roadmap-ru.md +0 -1879
- package/docs/fundraising/README.md +0 -87
- package/docs/fundraising/data-room-checklist.md +0 -72
- package/docs/fundraising/demo-script.md +0 -52
- package/docs/fundraising/design-partners-50.md +0 -74
- package/docs/fundraising/launch-posts.md +0 -37
- package/docs/fundraising/metrics-and-evidence.md +0 -30
- package/docs/fundraising/roadmap-12-month.md +0 -25
- package/docs/fundraising/technical-due-diligence-index.md +0 -37
- package/docs/fundraising/yc-application.md +0 -25
- package/docs/hosted-pilot.md +0 -111
- package/docs/operations/disaster-recovery-runbook.md +0 -81
- package/docs/operations/kill-switch-runbook.md +0 -32
- package/docs/release-checklist.md +0 -83
- package/docs/release-migration-and-rollback.md +0 -63
- package/docs/release-readiness.md +0 -39
- package/docs/team-control-plane.md +0 -72
package/docs/usage.md
CHANGED
|
@@ -1,571 +1,680 @@
|
|
|
1
|
-
# GhostAPI Usage Guide
|
|
2
|
-
|
|
3
|
-
## First Ten Minutes
|
|
4
|
-
|
|
5
|
-
Use these commands in a fresh project. They do not require production credentials:
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npx @yiaany/ghostapi init
|
|
9
|
-
npx @yiaany/ghostapi doctor
|
|
10
|
-
npx @yiaany/ghostapi run -- npm test
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
`init` writes `.ghostapi/config.json`, `ghostapi.policy.yaml`, MCP snippets, and agent instructions without overwriting existing files. On Linux, `run` performs a fail-closed namespace preflight before launching the target. On Windows and macOS, `run` is unsupported/experimental and fails closed; use `start --open` for local provider simulation and run enforced CI on Linux.
|
|
14
|
-
|
|
15
|
-
## Start The Local API
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npx @yiaany/ghostapi start --open
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
GhostAPI listens on:
|
|
22
|
-
|
|
23
|
-
```text
|
|
24
|
-
http://127.0.0.1:8080
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Dashboard:
|
|
28
|
-
|
|
29
|
-
```text
|
|
30
|
-
http://127.0.0.1:8080/dashboard
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Loopback is the convenient default. A non-loopback bind requires `GHOSTAPI_AUTH_TOKEN` with at least 24 characters:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
GHOSTAPI_AUTH_TOKEN="replace-with-a-long-random-token" ghostapi start --host 0.0.0.0 --https --open
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Remote access protects dashboard/control routes with the token, but the token does not encrypt traffic. Use HTTPS or a secure tunnel. Provider simulation routes are not authenticated and GhostAPI does not provide network isolation.
|
|
40
|
-
|
|
41
|
-
## Egress Diagnosis
|
|
42
|
-
|
|
43
|
-
Before relying on `ghostapi run`, inspect the host-specific boundary that GhostAPI can honestly provide:
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
ghostapi doctor --egress
|
|
47
|
-
ghostapi doctor --egress --json
|
|
48
|
-
ghostapi doctor --json
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
The diagnostic is stable for CI/tooling and identifies Node version, data-directory write permission, port availability, TLS bypass settings, config problems, platform primitives, required setup, and remaining bypasses without probing production network access. It is not proof that a run succeeded. See [`docs/security/egress-threat-model.md`](security/egress-threat-model.md) for the security model.
|
|
52
|
-
|
|
53
|
-
## Run With Linux Egress Enforcement
|
|
54
|
-
|
|
55
|
-
On Linux hosts where unprivileged user, mount, network and PID namespaces are available, run a command with a loopback-only network namespace:
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
ghostapi run -- npm test
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
The target and all ordinary descendants have no external route, DNS path or non-loopback IP interface. GhostAPI starts inside the same namespace at `http://127.0.0.1:8080`; the target receives `GHOSTAPI_BASE_URL`, `GHOSTAPI_HOST`, `GHOSTAPI_PORT`, `GHOSTAPI_PROTOCOL`, and `GHOSTAPI_OPENAI_BASE_URL`.
|
|
62
|
-
|
|
63
|
-
`ghostapi run` fails closed on Windows, macOS, hosts without a successful namespace preflight, and any external `--allow-host` value. There is no proxy-only fallback. The current backend does not transparently intercept provider TLS hostnames or provide individual audit records for kernel-denied socket attempts. Do not mount or expose same-user container-control UNIX sockets to untrusted code; this is not a hostile-code filesystem sandbox.
|
|
64
|
-
|
|
65
|
-
Each run writes sanitized lifecycle evidence under `.ghostapi/runs/<run-id>/run.json`. The command, argument values and environment secrets are not persisted there; allowed GhostAPI request traffic remains in the run's isolated GhostAPI event log.
|
|
66
|
-
|
|
67
|
-
## Evidence Reports
|
|
68
|
-
|
|
69
|
-
Turn run lifecycle evidence, persisted traffic events and an optional policy into a canonical JSON artifact:
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
ghostapi evidence generate --policy ghostapi.policy.yaml --ci
|
|
73
|
-
ghostapi evidence view .ghostapi/reports/latest.json
|
|
74
|
-
ghostapi evidence compare .ghostapi/reports/base.json .ghostapi/reports/head.json
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
The report includes run identity, timestamps, GhostAPI version, enforcement capability, policy hash, covered providers/scenarios, allowed GhostAPI attempts, secret categories, retry/failure counts, findings and incomplete-evidence warnings. It intentionally does not include request authorization, cookies, raw body secrets, command arguments or raw policy content.
|
|
78
|
-
|
|
79
|
-
Artifacts use schema version `1`, a stable logical hash over sorted JSON keys, a 512 KiB file limit and local retention of the latest 20 generated reports under `.ghostapi/reports/`. `ghostapi evidence view` rejects corrupted artifacts whose logical hash no longer matches their contents. `--ci` exits non-zero when fail findings are present.
|
|
80
|
-
|
|
81
|
-
When using `ghostapi run`, pass its exact `.ghostapi/runs/<run-id>/run.json` to `evidence generate --run`. GhostAPI then reads the isolated runtime event log for that run, rather than unrelated host/workspace events. See [`docs/github-actions.md`](github-actions.md) for the pinned GitHub Actions workflow and [`docs/ci.md`](ci.md) for generic CI integration.
|
|
82
|
-
|
|
83
|
-
## Agent Evals
|
|
84
|
-
|
|
85
|
-
Use `ghostapi eval` to score an agent workflow against deterministic GhostAPI evidence:
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
ghostapi eval --template retry-after --evidence .ghostapi/reports/latest.json --ci
|
|
89
|
-
ghostapi eval --spec examples/evals/retry-after.eval.json --evidence .ghostapi/reports/latest.json --json
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Without `--evidence`, the eval command launches `task.command` through the existing `ghostapi run` boundary and then generates evidence for that run. It does not execute an unknown command directly. Specs that declare `injectedFailures` currently require pre-generated evidence: GhostAPI rejects execution rather than pretending those failures were applied. On unsupported hosts, `ghostapi run` still fails closed instead of falling back to a proxy-only mode.
|
|
93
|
-
|
|
94
|
-
Eval specs are local JSON data only. Schema v1 describes `syntheticWorld`, `task.command`, `injectedFailures`, deterministic `expectations`, `forbidden` actions, `limits`, and a points `rubric`. Unknown fields, oversized specs, symlinks, path traversal, enabled LLM judge settings, duplicate rubric references, and incomplete point totals are rejected. Built-in templates cover retry honoring `Retry-After`, duplicate payment prevention, webhook signature validation, no secret in logs, timeout recovery, and no production bypass.
|
|
95
|
-
|
|
96
|
-
Core security score uses facts from sanitized evidence only. LLM-as-judge is optional future commentary and is never part of the core score. Forbidden actions such as production egress or secret leakage override cosmetic success and force the core score to `0`.
|
|
97
|
-
|
|
98
|
-
## Stateful Synthetic Worlds
|
|
99
|
-
|
|
100
|
-
Create a portable, deterministic local world for multi-provider integration workflows:
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
ghostapi world create --id subscription-recovery --seed demo-seed
|
|
104
|
-
ghostapi world inspect subscription-recovery
|
|
105
|
-
ghostapi world reset subscription-recovery
|
|
106
|
-
ghostapi world fork subscription-recovery --id subscription-recovery-investigation
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Schema-v1 worlds live under `.ghostapi/worlds/<id>.world.json`. A manifest defines one canonical synthetic persona and organization, an UTC clock, relationships, provider accounts/resources, and projections for Stripe, GitHub, email, and generic REST. A fixed `id`, `title`, and `seed` always generates the same initial manifest and baseline. Use non-secret, non-PII labels and seeds. A scenario/eval can pin this deterministic input with:
|
|
110
|
-
|
|
111
|
-
```json
|
|
112
|
-
{
|
|
113
|
-
"syntheticWorld": {
|
|
114
|
-
"world": {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
The
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
ghostapi
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
```bash
|
|
335
|
-
ghostapi
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
ghostapi contract
|
|
349
|
-
--
|
|
350
|
-
--
|
|
351
|
-
--
|
|
352
|
-
--
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
-
|
|
369
|
-
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
##
|
|
385
|
-
|
|
386
|
-
```ts
|
|
387
|
-
import
|
|
388
|
-
|
|
389
|
-
export const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
ghostapi
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
1
|
+
# GhostAPI Usage Guide
|
|
2
|
+
|
|
3
|
+
## First Ten Minutes
|
|
4
|
+
|
|
5
|
+
Use these commands in a fresh project. They do not require production credentials:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @yiaany/ghostapi init
|
|
9
|
+
npx @yiaany/ghostapi doctor
|
|
10
|
+
npx @yiaany/ghostapi run -- npm test
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`init` writes `.ghostapi/config.json`, `ghostapi.policy.yaml`, MCP snippets, and agent instructions without overwriting existing files. On Linux, `run` performs a fail-closed namespace preflight before launching the target. On Windows and macOS, `run` is unsupported/experimental and fails closed; use `start --open` for local provider simulation and run enforced CI on Linux.
|
|
14
|
+
|
|
15
|
+
## Start The Local API
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx @yiaany/ghostapi start --open
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
GhostAPI listens on:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
http://127.0.0.1:8080
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Dashboard:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
http://127.0.0.1:8080/dashboard
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Loopback is the convenient default. A non-loopback bind requires `GHOSTAPI_AUTH_TOKEN` with at least 24 characters:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
GHOSTAPI_AUTH_TOKEN="replace-with-a-long-random-token" ghostapi start --host 0.0.0.0 --https --open
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Remote access protects dashboard/control routes with the token, but the token does not encrypt traffic. Use HTTPS or a secure tunnel. Provider simulation routes are not authenticated and GhostAPI does not provide network isolation.
|
|
40
|
+
|
|
41
|
+
## Egress Diagnosis
|
|
42
|
+
|
|
43
|
+
Before relying on `ghostapi run`, inspect the host-specific boundary that GhostAPI can honestly provide:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
ghostapi doctor --egress
|
|
47
|
+
ghostapi doctor --egress --json
|
|
48
|
+
ghostapi doctor --json
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The diagnostic is stable for CI/tooling and identifies Node version, data-directory write permission, port availability, TLS bypass settings, config problems, platform primitives, required setup, and remaining bypasses without probing production network access. It is not proof that a run succeeded. See [`docs/security/egress-threat-model.md`](security/egress-threat-model.md) for the security model.
|
|
52
|
+
|
|
53
|
+
## Run With Linux Egress Enforcement
|
|
54
|
+
|
|
55
|
+
On Linux hosts where unprivileged user, mount, network and PID namespaces are available, run a command with a loopback-only network namespace:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
ghostapi run -- npm test
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The target and all ordinary descendants have no external route, DNS path or non-loopback IP interface. GhostAPI starts inside the same namespace at `http://127.0.0.1:8080`; the target receives `GHOSTAPI_BASE_URL`, `GHOSTAPI_HOST`, `GHOSTAPI_PORT`, `GHOSTAPI_PROTOCOL`, and `GHOSTAPI_OPENAI_BASE_URL`.
|
|
62
|
+
|
|
63
|
+
`ghostapi run` fails closed on Windows, macOS, hosts without a successful namespace preflight, and any external `--allow-host` value. There is no proxy-only fallback. The current backend does not transparently intercept provider TLS hostnames or provide individual audit records for kernel-denied socket attempts. Do not mount or expose same-user container-control UNIX sockets to untrusted code; this is not a hostile-code filesystem sandbox.
|
|
64
|
+
|
|
65
|
+
Each run writes sanitized lifecycle evidence under `.ghostapi/runs/<run-id>/run.json`. The command, argument values and environment secrets are not persisted there; allowed GhostAPI request traffic remains in the run's isolated GhostAPI event log.
|
|
66
|
+
|
|
67
|
+
## Evidence Reports
|
|
68
|
+
|
|
69
|
+
Turn run lifecycle evidence, persisted traffic events and an optional policy into a canonical JSON artifact:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
ghostapi evidence generate --policy ghostapi.policy.yaml --ci
|
|
73
|
+
ghostapi evidence view .ghostapi/reports/latest.json
|
|
74
|
+
ghostapi evidence compare .ghostapi/reports/base.json .ghostapi/reports/head.json
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The report includes run identity, timestamps, GhostAPI version, enforcement capability, policy hash, covered providers/scenarios, allowed GhostAPI attempts, secret categories, retry/failure counts, findings and incomplete-evidence warnings. It intentionally does not include request authorization, cookies, raw body secrets, command arguments or raw policy content.
|
|
78
|
+
|
|
79
|
+
Artifacts use schema version `1`, a stable logical hash over sorted JSON keys, a 512 KiB file limit and local retention of the latest 20 generated reports under `.ghostapi/reports/`. `ghostapi evidence view` rejects corrupted artifacts whose logical hash no longer matches their contents. `--ci` exits non-zero when fail findings are present.
|
|
80
|
+
|
|
81
|
+
When using `ghostapi run`, pass its exact `.ghostapi/runs/<run-id>/run.json` to `evidence generate --run`. GhostAPI then reads the isolated runtime event log for that run, rather than unrelated host/workspace events. See [`docs/github-actions.md`](github-actions.md) for the pinned GitHub Actions workflow and [`docs/ci.md`](ci.md) for generic CI integration.
|
|
82
|
+
|
|
83
|
+
## Agent Evals
|
|
84
|
+
|
|
85
|
+
Use `ghostapi eval` to score an agent workflow against deterministic GhostAPI evidence:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
ghostapi eval --template retry-after --evidence .ghostapi/reports/latest.json --ci
|
|
89
|
+
ghostapi eval --spec examples/evals/retry-after.eval.json --evidence .ghostapi/reports/latest.json --json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Without `--evidence`, the eval command launches `task.command` through the existing `ghostapi run` boundary and then generates evidence for that run. It does not execute an unknown command directly. Specs that declare `injectedFailures` currently require pre-generated evidence: GhostAPI rejects execution rather than pretending those failures were applied. On unsupported hosts, `ghostapi run` still fails closed instead of falling back to a proxy-only mode.
|
|
93
|
+
|
|
94
|
+
Eval specs are local JSON data only. Schema v1 describes `syntheticWorld`, `task.command`, `injectedFailures`, deterministic `expectations`, `forbidden` actions, `limits`, and a points `rubric`. Unknown fields, oversized specs, symlinks, path traversal, enabled LLM judge settings, duplicate rubric references, and incomplete point totals are rejected. Built-in templates cover retry honoring `Retry-After`, duplicate payment prevention, webhook signature validation, no secret in logs, timeout recovery, and no production bypass.
|
|
95
|
+
|
|
96
|
+
Core security score uses facts from sanitized evidence only. LLM-as-judge is optional future commentary and is never part of the core score. Forbidden actions such as observed production egress or secret leakage override cosmetic success and force the core score to `0`. The current backend does not award a zero production-egress score because it does not count kernel-denied socket attempts; a completed namespace run records the boundary lifecycle only. A retry score requires a retryable response followed by a matching later request. Eval reports include a stable logical hash, evidence hash/link, component-level reasons, repeatability notes, and no raw secrets.
|
|
97
|
+
|
|
98
|
+
## Stateful Synthetic Worlds
|
|
99
|
+
|
|
100
|
+
Create a portable, deterministic local world for multi-provider integration workflows:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
ghostapi world create --id subscription-recovery --seed demo-seed
|
|
104
|
+
ghostapi world inspect subscription-recovery
|
|
105
|
+
ghostapi world reset subscription-recovery
|
|
106
|
+
ghostapi world fork subscription-recovery --id subscription-recovery-investigation
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Schema-v1 worlds live under `.ghostapi/worlds/<id>.world.json`. A manifest defines one canonical synthetic persona and organization, an UTC clock, relationships, provider accounts/resources, and projections for Stripe, GitHub, email, and generic REST. A fixed `id`, `title`, and `seed` always generates the same initial manifest and baseline. Use non-secret, non-PII labels and seeds. A scenario/eval can pin this deterministic input with:
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"syntheticWorld": {
|
|
114
|
+
"world": {
|
|
115
|
+
"id": "subscription-recovery",
|
|
116
|
+
"version": "1.0.0",
|
|
117
|
+
"seed": "demo-seed"
|
|
118
|
+
},
|
|
119
|
+
"providers": ["stripe", "github", "resend", "generic"],
|
|
120
|
+
"scenarios": ["stripe-subscription-payment-failed"]
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The included [`examples/worlds/subscription-recovery.mjs`](../examples/worlds/subscription-recovery.mjs) models an atomic recovery flow: create a synthetic Stripe customer, put its subscription into `past_due`, record a generic REST payment failure, send a synthetic `ghostapi.invalid` email, and open a GitHub recovery issue. Every projected record references the same canonical identities and subscription ID. The workflow returns a receipt and is idempotent by action ID.
|
|
126
|
+
|
|
127
|
+
World transitions use one local file lock and same-directory atomic replacement. They are strongly consistent only for processes using the same world file on one local filesystem; GhostAPI does not claim distributed coordination, cloud tenancy, provider parity, or external delivery. Worlds are data only, capped at 512 KiB and 100 receipts, reject symlink files, secret-shaped values/fields, and non-`ghostapi.invalid` email addresses. `reset` restores the original baseline; `fork` snapshots the source's current state with lineage and then evolves independently.
|
|
128
|
+
|
|
129
|
+
## Synthetic Action Gateway
|
|
130
|
+
|
|
131
|
+
`ghostapi action` is the local foundation for one typed action contract across simulation, evidence, approvals, and a future execution gateway. In this release, its only adapter is `ghostapi-synthetic`; it executes `synthetic.subscription_failure` against an existing local synthetic world. It has no production provider account, credential, HTTP client, shell/tool execution path, message delivery, payment, deployment, or other external side effect.
|
|
132
|
+
|
|
133
|
+
An action envelope is schema-v1 JSON containing a stable `actionId`, `idempotencyKey`, agent/workload identity, project/environment, provider/operation/resource, normalized arguments, expected effects, risk/reversibility classification, policy/evidence references, expiry, and nonce. The complete normalized envelope is canonicalized with sorted object keys and SHA-256 hashed. Approval is its own schema-v1 object containing the exact `actionHash`, named independent approver, issue/expiry timestamps, and nonce; a boolean approval is not accepted.
|
|
134
|
+
|
|
135
|
+
## Local Action Ledger And Incident Replay
|
|
136
|
+
|
|
137
|
+
`createLocalActionLedger()` is a local typed API, not a CLI or hosted audit service. It records a tenant-scoped, append-only action timeline from an existing `StoredAction` returned by the synthetic action gateway. The timeline connects intent, identity, policy decision, approval, credential-grant reference, execution attempts, provider receipts, verification/reconciliation, and compensation status.
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
const access = createTestLedgerAccessAuthorizer(); // Test helper only.
|
|
141
|
+
const tenantAudit = access.issue({
|
|
142
|
+
tenantId: "tenant-a",
|
|
143
|
+
principalId: "audit-service",
|
|
144
|
+
permissions: ["append", "read", "export"],
|
|
145
|
+
});
|
|
146
|
+
const ledger = createLocalActionLedger({ accessAuthorizer: access.authorizer });
|
|
147
|
+
|
|
148
|
+
await ledger.recordAction(tenantAudit, await gateway.inspect("action-one"));
|
|
149
|
+
const timeline = await ledger.timeline(tenantAudit, "action-one");
|
|
150
|
+
const exported = await ledger.exportTenant(tenantAudit);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Every entry uses SHA-256 over canonical structured data plus the previous tenant entry hash. Verification fails if content, ordering, a chain link, head hash, or entry count changes. Export first verifies the requested tenant chain and returns only that tenant's entries. The ledger rejects raw payloads and credential/PII-shaped fields; it records hashes or safe scalar references instead.
|
|
154
|
+
|
|
155
|
+
Turn an action with a confirmed or ambiguous outcome into a local regression fixture:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
const incident = await ledger.createIncidentFixture(tenantAudit, "action-one");
|
|
159
|
+
const result = await ledger.replayIncidentFixture(
|
|
160
|
+
tenantAudit,
|
|
161
|
+
incident.fixture,
|
|
162
|
+
);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The pipeline creates a deterministic synthetic world and one sanitized, sequence-strict local scenario bundle. It does not open a network connection, call a provider, reuse an original credential, or copy an action payload into the ledger. An ambiguous or unverified result reproduces as `409 requires_reconciliation`, never as success. Add the generated `*.fixture.json` and `*.bundle.json` to a normal Vitest/CI fixture test to make the incident a regression check.
|
|
166
|
+
|
|
167
|
+
Retention is per-tenant and opt-in: a tenant with a configured `retentionDays` is bounded by the 2,000-entry store cap — when the cap is reached, entries older than the retention window are rotated out and the tenant's chain is relinked so verification still passes. A tenant without a retention policy, and any tenant on a local legal hold, is never rotated. Local legal hold blocks deletion requests, and a deletion request is only an auditable request, not an erasure or compliance claim. See the [action-ledger and incident-replay threat model](security/action-ledger-incident-replay-threat-model.md).
|
|
168
|
+
|
|
169
|
+
Use the public API to construct the exact action, then execute it through a verifier-backed approval inbox. Load the Ed25519 private key from a durable protected secret store available only to the approval authority and load the matching public key in the executor. Do not generate a new key for each process start.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import {
|
|
173
|
+
createApprovalInboxExecutionFlow,
|
|
174
|
+
createEd25519ActionApprovalSigner,
|
|
175
|
+
createEd25519ActionApprovalVerifier,
|
|
176
|
+
} from "@yiaany/ghostapi";
|
|
177
|
+
|
|
178
|
+
const action = {
|
|
179
|
+
schemaVersion: 1,
|
|
180
|
+
kind: "ghostapi.action",
|
|
181
|
+
actionId: "recover-subscription-001",
|
|
182
|
+
idempotencyKey: "recover-subscription-001",
|
|
183
|
+
actor: { id: "billing-agent", workloadId: "recovery-worker", type: "agent" },
|
|
184
|
+
project: { id: "checkout", environment: "synthetic" },
|
|
185
|
+
provider: "ghostapi-synthetic",
|
|
186
|
+
operation: "synthetic.subscription_failure",
|
|
187
|
+
resource: { type: "synthetic-world", id: "subscription-recovery" },
|
|
188
|
+
arguments: { worldId: "subscription-recovery" },
|
|
189
|
+
expectedSideEffects: [
|
|
190
|
+
"stripe.subscription.past_due",
|
|
191
|
+
"email.subscription_payment_failed",
|
|
192
|
+
"github.recovery_issue",
|
|
193
|
+
"generic_rest.payment_failed",
|
|
194
|
+
],
|
|
195
|
+
riskClass: "write",
|
|
196
|
+
reversibility: "none",
|
|
197
|
+
policy: { version: 1, hash: "<SHA-256 of reviewed policy source>" },
|
|
198
|
+
evidence: { hash: "<SHA-256 evidence reference>" },
|
|
199
|
+
expiresAt: "2030-01-01T00:00:00.000Z",
|
|
200
|
+
nonce: "review-001",
|
|
201
|
+
} as const;
|
|
202
|
+
|
|
203
|
+
const approvalSigner = createEd25519ActionApprovalSigner({
|
|
204
|
+
keyId: "local-approval-2026",
|
|
205
|
+
privateKey: privateKeyPem,
|
|
206
|
+
});
|
|
207
|
+
const approvalVerifier = createEd25519ActionApprovalVerifier({
|
|
208
|
+
trustedKeys: { "local-approval-2026": publicKeyPem },
|
|
209
|
+
});
|
|
210
|
+
const inbox = createApprovalInboxExecutionFlow({
|
|
211
|
+
approverVerifier,
|
|
212
|
+
approvalSigner,
|
|
213
|
+
approvalVerifier,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const approvalPolicy = {
|
|
217
|
+
schemaVersion: 1,
|
|
218
|
+
kind: "ghostapi.approval-policy",
|
|
219
|
+
id: "synthetic-recovery",
|
|
220
|
+
version: 1,
|
|
221
|
+
allowedEnvironments: ["synthetic"],
|
|
222
|
+
minimumConfidence: 80,
|
|
223
|
+
criticalRisks: ["update"],
|
|
224
|
+
velocity: { maxActions: 10, windowMs: 60_000 },
|
|
225
|
+
approvalTtlMs: 10 * 60_000,
|
|
226
|
+
escalationTimeoutMs: 5 * 60_000,
|
|
227
|
+
} as const;
|
|
228
|
+
|
|
229
|
+
const request = await inbox.request(action, approvalPolicy, { confidence: 95 });
|
|
230
|
+
await inbox.approve(request.id, authenticatedReviewerOne);
|
|
231
|
+
await inbox.approve(request.id, authenticatedReviewerTwo);
|
|
232
|
+
const receipt = await inbox.execute(
|
|
233
|
+
request.id,
|
|
234
|
+
{ actorId: "billing-agent", workloadId: "recovery-worker" },
|
|
235
|
+
{ version: 1, hash: action.policy.hash, allowed: true },
|
|
236
|
+
approvalPolicy,
|
|
237
|
+
);
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The action CLI is intentionally inspect-only because policy schema v1 has no action-level authorization rule and the CLI has no approver authentication or signing-key boundary:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
ghostapi action inspect recover-subscription-001 --json
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Before the synthetic side effect, the gateway verifies the Ed25519 approval provenance again from the persisted action record, then checks exact action hash, independent approval, action/approval expiry, current policy version/hash, execution actor/workload identity, adapter operation support, and idempotency state. Inbox artifacts additionally require durable consumed inbox state; a signing-key-only gateway cannot execute them. Missing, unknown, invalid, or unavailable verification authority fails closed. Receipts progress through `requested`, `attempted`, `committed`, `verified`, or `failed`; an attempted action is reconciled before any re-execution. If a process stops after the inbox records `consumedAt`/`executing`, call `inbox.execute()` again with the same request, identity, action policy, and approval policy. Recovery uses the gateway's durable receipt chain, never consumes a second approval, and repeated calls return the same verified receipt without repeating the side effect. Expiry still blocks a first attempt, while an already durable `attempted`, `committed`, or `verified` receipt may be reconciled or returned after expiry without starting another attempt. A durable failed receipt is terminal; an ambiguous outcome remains fail-closed for reconciliation. Unknown provider outcomes and verification failures are explicitly not safe to retry. Unsupported operations or compensation fail visibly. The local store is bounded to 128 KiB per record and 20 receipts, uses per-action locks and atomic writes, and rejects symlink records. Legacy unsigned approval records are intentionally not executable.
|
|
247
|
+
|
|
248
|
+
Policy schema v1 does not yet contain action-level authorization rules. This release verifies the reviewed policy reference at execution to prevent stale-policy use; it does not claim that a generic policy authorizes a production action. Read [`docs/security/action-gateway-threat-model.md`](security/action-gateway-threat-model.md) before extending the adapter boundary.
|
|
249
|
+
|
|
250
|
+
## Credential Broker And Workload Identity
|
|
251
|
+
|
|
252
|
+
The public `CredentialBroker` API is a local foundation for keeping upstream provider secrets outside an AI agent's context. It has no CLI, MCP tool, HTTP endpoint, provider SDK, environment-secret loader, or API that returns a provider secret. A grant is always audience-bound to `ghostapi-server`, so the agent process cannot receive it as an environment variable, argument, stdin value, log, report, or response.
|
|
253
|
+
|
|
254
|
+
The broker stores only credential metadata, scoped grant metadata, and action-linked execution receipts in `.ghostapi/credential-broker.json`. Secret material remains behind an injected vault interface. This repository intentionally ships only a test in-memory vault and test executor for automated integration tests; neither has network or provider capability. Production use requires an existing reviewed vault/KMS abstraction and a separately reviewed server-side provider executor. Do not implement homemade reversible encryption or place secret material in `.env`, fixtures, scenario bundles, approval records, or MCP responses.
|
|
255
|
+
|
|
256
|
+
Workload identities distinguish `agent_run`, `ci_job`, and `production_service` and bind a tenant, project, environment, workload, subject, run, issue time, and expiry. Before each server-side execution the broker rechecks workload identity, credential/grant revocation and expiry, tenant/project/environment, provider, scope, server-only audience, credential version after rotation, and exact action ID/hash/verified receipt reference. Standard grants last at most 15 minutes. Break-glass is disabled unless an injected independent human-controlled authorizer validates an exact-action approval, and its grants last at most 5 minutes.
|
|
257
|
+
|
|
258
|
+
Rotation changes the opaque vault reference, increments the credential version, and revokes active grants without touching local simulation state. Revocation blocks all new execution before vault access. `listOrphanedCredentials()` reports active credentials whose configured owner workload is no longer active; it does not delete or transfer them automatically. Read [`docs/security/credential-broker-threat-model.md`](security/credential-broker-threat-model.md) before implementing any vault, identity, approval, or provider adapter.
|
|
259
|
+
|
|
260
|
+
## Local Approval Inbox
|
|
261
|
+
|
|
262
|
+
`createApprovalInboxExecutionFlow()` and `createLocalApprovalInbox()` expose the supported typed local human-approval execution flow over the existing synthetic action gateway. They require an approver identity verifier, approval signer, and approval signature verifier. They have no execution CLI, hosted UI, Slack/email integration, bearer approval link, external notification, real credential, or production provider path. Messages and LLM text are never approval authority.
|
|
263
|
+
|
|
264
|
+
Requests are generated from an exact action envelope and display the intent, target, normalized argument diff, expected side effects, reversibility, amount impact, policy reason, evidence hash, and successful synthetic preflight result. The risk taxonomy includes `read`, `create`, `update`, `communicate`, `money_movement`, `delete`, `permission_change`, and `deployment`; risk is derived by GhostAPI, not supplied by an agent. The current synthetic subscription workflow is an `update` action only.
|
|
265
|
+
|
|
266
|
+
Policies can restrict environment, actor, resource, amount, confidence, and action velocity. Actions requiring two-person review require distinct verified principals and approver independence keys. Approval artifacts are exact-action-hash-bound, expiring, single-use, and Ed25519-signed. The gateway re-verifies the signature at submit and execution, while the inbox execution verifier also checks the durable consumed request state. An `executing` request is safely resumable with the same inputs: durable gateway receipts decide whether to continue, reconcile, return the existing verified result, or fail closed, and the inbox links the verified receipt before becoming `executed`. A caller holding only artifact JSON or the public verification key cannot bypass consumption, revoke, recovery checks, or inbox audit state. Reject, revoke, timeout, expiry, changed arguments, changed identity, policy drift, missing verification keys, and invalid signatures all fail closed. Read [`docs/security/approval-inbox-threat-model.md`](security/approval-inbox-threat-model.md) before connecting any future identity, notification, or provider execution system.
|
|
267
|
+
|
|
268
|
+
## Local Synthetic Trust Ladder
|
|
269
|
+
|
|
270
|
+
`createLocalTrustLadder()` is a data-only preparation layer for the typed synthetic action contract. It does not call the action gateway, approval inbox, credential broker, vault, provider SDK, HTTP client, or a real provider. Its only target identity is `{ provider: "ghostapi-synthetic", environment: "synthetic" }`; production/test-account identity mixing is rejected.
|
|
271
|
+
|
|
272
|
+
| Trust level | Local synthetic capability | External side effects |
|
|
273
|
+
| -------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------- |
|
|
274
|
+
| `simulate` | Supported local preparation state. | Never. |
|
|
275
|
+
| `shadow` | Supported hash-only comparison of predicted action/context with supplied actual input context. | Never. |
|
|
276
|
+
| `dry-run` | Unsupported because `ghostapi-synthetic` has no provider-official dry-run semantic. It is never substituted with execution. | Never. |
|
|
277
|
+
| `approve` | Supported preparation state that denotes the existing approval boundary requirement. | Never. |
|
|
278
|
+
| `bounded-auto` | Supported preparation state with deterministic canary eligibility and predicted/actual outcome comparison evidence. | Never. |
|
|
279
|
+
| `trusted` | Unsupported. Local synthetic state is not production authorization. | Never. |
|
|
280
|
+
|
|
281
|
+
Promotion is owner-gated and never automatic: a verified stable owner principal must explicitly advance exactly one supported step and provide fresh evidence meeting minimum-run, required-eval, violation-rate, and error-rate policy thresholds. Canary scope is tenant/resource/percentage based and assigned from a stable SHA-256 bucket. Violations can auto-demote to `approve` or open a circuit breaker according to policy. Once a breaker is open or a configured stop condition is breached, the controller rejects further canary activity. `rollbackToApproval()` records its reason in the bounded local audit chain. The store contains hashes and bounded metadata only under `.ghostapi/trust-ladder.json`; it is not a durable production audit ledger or authorization system. Read [`docs/security/trust-ladder-threat-model.md`](security/trust-ladder-threat-model.md) before extending any execution path.
|
|
282
|
+
|
|
283
|
+
## Local Kill Switch, Budgets, And Blast Radius
|
|
284
|
+
|
|
285
|
+
`createLocalSafetyController()` is a local typed API for the existing `ghostapi-synthetic` action path. It persists kill switches scoped to global, organization, project, environment, agent, workload, provider, operation, or risk class. It also tracks monetary amount, request, message, mutation, delete, token-cost, concurrency, and velocity budgets under a lock, then opens persisted circuit breakers for configured failure-rate, policy-violation, latency, or reconciliation-mismatch conditions.
|
|
286
|
+
|
|
287
|
+
Emergency changes require an injected `SafetyEmergencyAuthorizer`. `safety.stop`, `safety.reenable`, and `safety.configure` are explicit distinct permissions, and every operation needs a safe bounded reason. There is no unauthenticated HTTP endpoint, no Slack/email/webhook approval transport, and no hosted control plane. The controller is local synthetic preparation and enforcement, not a provider kill switch.
|
|
288
|
+
|
|
289
|
+
Action approval is not a bypass: action execution admits a persisted budget reservation, and the synthetic world invokes the controller immediately before its atomic local commit. If a kill switch wins before that final check, the mutation is blocked. A side effect that already crossed the commit point is not claimed reversible. Replays with the exact idempotency/action hash do not consume a second budget; changed actions with the same idempotency key fail closed. Queues are bounded at 100 entries and stopped queued work moves to the local dead-letter queue without automatic retry.
|
|
290
|
+
|
|
291
|
+
The scheduled `GhostAPI Kill Switch Game Day` workflow runs the non-destructive local synthetic drill every Monday at `03:17 UTC`. Follow [`docs/operations/kill-switch-runbook.md`](operations/kill-switch-runbook.md) for detection, containment, investigation, recovery, and re-enable steps. Read [`docs/security/kill-switch-budgets-threat-model.md`](security/kill-switch-budgets-threat-model.md) before integrating any real provider action.
|
|
292
|
+
|
|
293
|
+
## Team Control-Plane Prototype
|
|
294
|
+
|
|
295
|
+
The repository records no independently verified customer, paid-pilot, or deployed-service evidence. The included prototype remains a local typed library, not a hosted account system or web UI. It models organizations, members/roles, projects, environments, versioned scenario metadata, sanitized CI evidence summaries, distributed policy versions, short-lived revocable tokens, review metadata, migrations, and bounded retention. The separate hosted experiment and its explicit deployment limits are documented in [`docs/hosted-pilot.md`](hosted-pilot.md).
|
|
296
|
+
|
|
297
|
+
Local runtime behavior is unchanged and does not require login. Cloud sync is not implemented. The prototype has no upload transport for raw traffic, code, secrets, request bodies, or provider credentials; evidence is accepted only after GhostAPI schema/hash validation and stored as a restricted summary. Read [`docs/team-control-plane.md`](team-control-plane.md) for the tenant model, API, storage limitations, and incident-response boundary.
|
|
298
|
+
|
|
299
|
+
## Optional Local Product Telemetry
|
|
300
|
+
|
|
301
|
+
GhostAPI has no telemetry by default. `ghostapi telemetry enable` stores only four local aggregate counters and up to eight ISO week labels in `.ghostapi/product-telemetry.json`; it has no network transport and does not record source code, traffic, commands, provider names, repository identity, credentials, or secrets. Inspect the aggregate with `ghostapi telemetry status` or `ghostapi telemetry export --json`. `ghostapi telemetry disable` deletes it. See [`docs/telemetry.md`](telemetry.md) for the retention and export boundary.
|
|
302
|
+
|
|
303
|
+
## Policy As Code
|
|
304
|
+
|
|
305
|
+
Use a strict local `ghostapi.policy.yaml` to make network, credential, scenario, enforcement and report decisions deterministic:
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
ghostapi policy validate
|
|
309
|
+
ghostapi policy explain network api.stripe.com --provider stripe
|
|
310
|
+
ghostapi policy explain stripe-payment-intent-card-declined
|
|
311
|
+
ghostapi run --policy ghostapi.policy.yaml -- npm test
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
See [`docs/policy.md`](policy.md) and the safe [`examples/policy/ghostapi.policy.yaml`](../examples/policy/ghostapi.policy.yaml). The policy language has no remote includes, interpolation, or executable expressions.
|
|
315
|
+
|
|
316
|
+
## Record And Replay Sandbox Traffic
|
|
317
|
+
|
|
318
|
+
Record only an explicit, reviewed sandbox host. GhostAPI accepts a bounded JSON capture with either an `interactions` array or HAR `log.entries`; it does not use a recording proxy and never stores a raw temporary capture:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
ghostapi record \
|
|
322
|
+
--input stripe-sandbox.har \
|
|
323
|
+
--allow-sandbox-host api.stripe.com \
|
|
324
|
+
--title "Checkout retry" \
|
|
325
|
+
--approve
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
`api.stripe.com` is accepted only when captured requests carry a Stripe test/restricted-test key. Other hosts must both be explicitly allowlisted and look sandbox/test-like. HTTPS is required. Production-looking, unknown, direct-IP, HTTP, and wildcard hosts fail closed.
|
|
329
|
+
|
|
330
|
+
Before writing the portable schema-v1 bundle, GhostAPI structurally removes authorization, cookies, secret-shaped fields and known keys; redacts emails, phones and address fields by default; turns known unstable IDs/timestamps into deterministic variables; drops multipart/binary bodies; and blocks external redirect targets. It prints a summary and requires `--approve` whenever any potentially sensitive category was found. `--pii none` or a narrower comma-separated list is available only for a deliberate, reviewed capture; it does not make secret masking optional.
|
|
331
|
+
|
|
332
|
+
Replay is entirely offline and sequence-strict. It accepts a JSON request array (or `{ "requests": [...] }`) and never scans ahead for a later matching interaction:
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
ghostapi replay .ghostapi/scenarios/checkout-retry.bundle.json --requests replay-requests.json --json
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
The first matched request can bind a recorded variable; later requests and synthetic responses reuse that binding. A mismatch, missing request, extra request, ambiguous alternate order, invalid schema, symlink, oversized file, secret-bearing bundle value, or executable/unknown field fails with diagnostics. Bundle files are limited to 512 KiB, captures and replay input to 1 MiB, and bundles contain data only: no hooks, filesystem paths, commands, or executable expressions. Schema v0 uses the documented local migration to v1 and is marked `legacy-bundle` for review; unsupported versions fail closed.
|
|
339
|
+
|
|
340
|
+
PII detection is intentionally heuristic, not a guarantee of anonymization. Review the summary and the saved sanitized bundle before sharing it. Do not capture production traffic.
|
|
341
|
+
|
|
342
|
+
## Contract Import And Drift
|
|
343
|
+
|
|
344
|
+
GhostAPI imports a deliberately small, local-only OpenAPI subset: JSON OpenAPI `3.0.x`, non-parameterized relative paths, standard HTTP methods, JSON request/response bodies, object/array/scalar schemas, `required`, and scalar `enum` values. It does not support YAML, OpenAPI 3.1, Swagger 2.0, `$ref` (including local refs), remote URLs, callbacks, links, composition, path/query/header parameter schemas, headers, security schemes, servers, or executable extensions. Unsupported input fails with an actionable error; the importer never opens a network connection.
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
ghostapi contract import-openapi --input openapi.json --out .ghostapi/contracts/orders.contract.json
|
|
348
|
+
ghostapi contract import-har \
|
|
349
|
+
--input sandbox.har \
|
|
350
|
+
--allow-sandbox-host api.sandbox.example \
|
|
351
|
+
--contract-out .ghostapi/contracts/sandbox.contract.json \
|
|
352
|
+
--approve
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
HAR import first passes through the exact same bounded sanitizer used by `ghostapi record`; it writes the sanitized scenario bundle and derives a contract only from that sanitized data. Contract input is limited to 1 MiB, output to 512 KiB, OpenAPI to 200 paths/400 operations, schemas to depth 20, and object schemas to 100 properties. ZIP and gzip archives are rejected before parsing or decompression; extract one reviewed JSON file first.
|
|
356
|
+
|
|
357
|
+
Compare contracts in CI without any live provider request:
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
ghostapi contract diff \
|
|
361
|
+
--baseline .ghostapi/contracts/base.contract.json \
|
|
362
|
+
--candidate .ghostapi/contracts/head.contract.json \
|
|
363
|
+
--policy ghostapi.policy.yaml \
|
|
364
|
+
--ci
|
|
365
|
+
|
|
366
|
+
ghostapi evidence generate \
|
|
367
|
+
--policy ghostapi.policy.yaml \
|
|
368
|
+
--contract-baseline .ghostapi/contracts/base.contract.json \
|
|
369
|
+
--contract-candidate .ghostapi/contracts/head.contract.json \
|
|
370
|
+
--ci
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
The diff deterministically reports added/removed endpoints, request required-field changes, enum/type changes, response status/schema changes, and provider-pack capability drift. Removed endpoints, removed response fields/statuses, narrowed enums, type changes, and lost pack capabilities are breaking. Added endpoints and optional request fields are non-breaking. Added response enum/status/fields and changed schema presence are `uncertain`, because client tolerance cannot be inferred. `reports.maxBreakingContractChanges` defaults to `0` when omitted and can be set explicitly in policy. Evidence artifacts include breaking/non-breaking/uncertain totals and fail CI according to the policy threshold.
|
|
374
|
+
|
|
375
|
+
## Send A Local Request
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
curl -X POST http://127.0.0.1:8080/v1/customers \
|
|
379
|
+
-H "content-type: application/json" \
|
|
380
|
+
-H "authorization: Bearer stripe_test_ghostapi" \
|
|
381
|
+
-d '{"email":"ada@example.com","name":"Ada Lovelace"}'
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Stripe SDK
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
import Stripe from "stripe";
|
|
388
|
+
|
|
389
|
+
export const stripe = new Stripe(
|
|
390
|
+
process.env.STRIPE_SECRET_KEY ?? "stripe_test_ghostapi",
|
|
391
|
+
{
|
|
392
|
+
host: process.env.GHOSTAPI_HOST ?? "127.0.0.1",
|
|
393
|
+
port: Number(process.env.GHOSTAPI_PORT ?? "8080"),
|
|
394
|
+
protocol: process.env.GHOSTAPI_PROTOCOL ?? "http",
|
|
395
|
+
},
|
|
396
|
+
);
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
## OpenAI SDK
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
import OpenAI from "openai";
|
|
403
|
+
|
|
404
|
+
export const openai = new OpenAI({
|
|
405
|
+
apiKey: process.env.OPENAI_API_KEY ?? "sk-ghostapi",
|
|
406
|
+
baseURL: process.env.GHOSTAPI_OPENAI_BASE_URL ?? "http://127.0.0.1:8080/v1",
|
|
407
|
+
});
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
The `OPENAI_API_KEY` above belongs to the application being pointed at the local GhostAPI endpoint; use a fake local value. It does not enable GhostAPI's own external LLM access.
|
|
411
|
+
|
|
412
|
+
## Starter Examples
|
|
413
|
+
|
|
414
|
+
- Stripe checkout and billing: [`examples/stripe-node`](../examples/stripe-node)
|
|
415
|
+
- OpenAI streaming/tool call: [`examples/openai-streaming`](../examples/openai-streaming)
|
|
416
|
+
- CI policy failure: [`examples/ci-smoke`](../examples/ci-smoke)
|
|
417
|
+
- Record/replay: [`examples/record-replay`](../examples/record-replay)
|
|
418
|
+
- Agent eval: [`examples/evals`](../examples/evals)
|
|
419
|
+
|
|
420
|
+
## Configuration
|
|
421
|
+
|
|
422
|
+
Local runtime config lives in `.ghostapi/config.json`. Environment variables override it for the current process: `GHOSTAPI_HOST`, `GHOSTAPI_PORT`, `GHOSTAPI_MODEL`, `GHOSTAPI_OFFLINE`, `GHOSTAPI_HTTPS`, `GHOSTAPI_ALLOW_EXTERNAL_LLM`, `GHOSTAPI_LLM_API_KEY`, `GHOSTAPI_AUTH_TOKEN`, and `GHOSTAPI_DATA_DIR`.
|
|
423
|
+
|
|
424
|
+
## Optional External LLM Generation
|
|
425
|
+
|
|
426
|
+
External generation is disabled by default, even when `OPENAI_API_KEY` exists in the environment. To opt in explicitly, provide both the capability flag and the GhostAPI-specific key:
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
GHOSTAPI_LLM_API_KEY="..." ghostapi start --allow-external-llm
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
The equivalent environment flag is `GHOSTAPI_ALLOW_EXTERNAL_LLM=true`. `--offline` overrides external access.
|
|
433
|
+
|
|
434
|
+
When external generation is enabled on a non-loopback bind, proxy requests also require `Authorization: Bearer <GHOSTAPI_AUTH_TOKEN>` or `X-GhostAPI-Token`. This prevents unauthenticated clients from consuming the configured external LLM account.
|
|
435
|
+
|
|
436
|
+
## Reliability: SLOs, Reconciliation, Cost Governance, And Runtime Health
|
|
437
|
+
|
|
438
|
+
The reliability layer is local-first and synthetic. It adds no provider, credential, or network surface.
|
|
439
|
+
|
|
440
|
+
### Runtime health
|
|
441
|
+
|
|
442
|
+
`GET /health` returns `{ ok, ready }` (liveness). `GET /health/readiness` returns the full structural report and HTTP 503 when a store is missing, oversized, corrupt, or a symlink. File stores are capped at 4 MiB each during the check.
|
|
443
|
+
|
|
444
|
+
```ts
|
|
445
|
+
import { checkRuntimeHealth, formatRuntimeHealth } from "@yiaany/ghostapi";
|
|
446
|
+
const report = await checkRuntimeHealth();
|
|
447
|
+
console.log(formatRuntimeHealth(report));
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### SLOs
|
|
451
|
+
|
|
452
|
+
```ts
|
|
453
|
+
import {
|
|
454
|
+
createLocalSloController,
|
|
455
|
+
createSloRecordIdentity,
|
|
456
|
+
createTestSloOperatorAuthorizer,
|
|
457
|
+
} from "@yiaany/ghostapi";
|
|
458
|
+
|
|
459
|
+
const { authorizer, issue } = createTestSloOperatorAuthorizer();
|
|
460
|
+
const operator = issue({
|
|
461
|
+
id: "sre",
|
|
462
|
+
principalId: "sre-one",
|
|
463
|
+
permissions: ["slo.configure", "slo.inspect"],
|
|
464
|
+
});
|
|
465
|
+
const controller = createLocalSloController({ operatorAuthorizer: authorizer });
|
|
466
|
+
await controller.configureTarget({
|
|
467
|
+
identity: operator,
|
|
468
|
+
target: {
|
|
469
|
+
id: "availability.checkout",
|
|
470
|
+
metric: "availability",
|
|
471
|
+
windowMs: 60 * 60 * 1000,
|
|
472
|
+
minimumSamples: 10,
|
|
473
|
+
targetBps: 9_000,
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const recordIdentity = createSloRecordIdentity(); // only the reconciliation service and its peers hold this
|
|
478
|
+
await controller.recordSample(
|
|
479
|
+
{
|
|
480
|
+
metric: "availability",
|
|
481
|
+
ok: true,
|
|
482
|
+
runId: "run-1",
|
|
483
|
+
actionId: "action-1",
|
|
484
|
+
labels: { tenantId: "tenant-a" },
|
|
485
|
+
},
|
|
486
|
+
recordIdentity,
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
const report = await controller.evaluate({ identity: operator });
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
Recording requires the record capability; configuring/evaluating requires an authenticated operator. Samples are trimmed to the evaluation window, capped per metric, and recorded in bounded batches of at most 1,000 per call. Latency SLIs only count samples that report `ok`.
|
|
493
|
+
|
|
494
|
+
### Reconciliation
|
|
495
|
+
|
|
496
|
+
```ts
|
|
497
|
+
import {
|
|
498
|
+
createLocalReconciliationService,
|
|
499
|
+
createWorldStateReconciliationProvider,
|
|
500
|
+
} from "@yiaany/ghostapi";
|
|
501
|
+
|
|
502
|
+
const service = createLocalReconciliationService({
|
|
503
|
+
ledger,
|
|
504
|
+
capability,
|
|
505
|
+
provider: createWorldStateReconciliationProvider(async (actionId) => worldId),
|
|
506
|
+
});
|
|
507
|
+
const report = await service.runReconciliation();
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Reconciliation exports the tenant ledger (blocked if integrity fails), classifies every action as `committed` / `not_committed` / `unknown` / `compensated` / `drifted`, derives SLI samples (duplicate-prevention, receipt verification, availability, execution latency), records them into the SLO controller, and opens findings for drifted/unknown actions. Resolutions require an operator with `reconciliation.manage`; unknown findings require provider evidence.
|
|
511
|
+
|
|
512
|
+
### Cost governance
|
|
513
|
+
|
|
514
|
+
```ts
|
|
515
|
+
import {
|
|
516
|
+
createLocalCostGovernance,
|
|
517
|
+
createTestCostOperatorAuthorizer,
|
|
518
|
+
} from "@yiaany/ghostapi";
|
|
519
|
+
|
|
520
|
+
const controller = createLocalCostGovernance({
|
|
521
|
+
operatorAuthorizer: authorizer,
|
|
522
|
+
});
|
|
523
|
+
await controller.recordCost({
|
|
524
|
+
identity: operator,
|
|
525
|
+
record: {
|
|
526
|
+
tenantId: "tenant-a",
|
|
527
|
+
runId: "run-1",
|
|
528
|
+
actionId: "action-1",
|
|
529
|
+
attribution,
|
|
530
|
+
amounts,
|
|
531
|
+
},
|
|
532
|
+
});
|
|
533
|
+
const report = await controller.report({ identity: operator });
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
`report` returns totals, attribution dimensions, budget statuses, and a linear-extrapolation forecast that is explicitly not a provider invoice. Exceeded budgets with `alertOnExceed` raise acknowledgeable alerts.
|
|
537
|
+
|
|
538
|
+
### Backup and restore
|
|
539
|
+
|
|
540
|
+
```ts
|
|
541
|
+
import { backupRuntime, restoreRuntimeBackup } from "@yiaany/ghostapi";
|
|
542
|
+
|
|
543
|
+
const backup = await backupRuntime({
|
|
544
|
+
destinationDir: ".ghostapi/reliability/backups/drill-1",
|
|
545
|
+
});
|
|
546
|
+
await restoreRuntimeBackup({
|
|
547
|
+
sourceDir: backup.path,
|
|
548
|
+
targetDir: ".ghostapi-restored",
|
|
549
|
+
});
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
Backups verify every file against a sha256 manifest, refuse to overwrite, and exclude only the canonical `cache`/`backups` data locations, top-level `runs`, lock and temp files, and symbolic links — nested folders named `cache` or `backups` are backed up normally. Restore re-verifies everything, rejects tampered or path-escaping manifests, and refuses a non-empty target directory. See the [disaster-recovery runbook](operations/disaster-recovery-runbook.md).
|
|
553
|
+
|
|
554
|
+
## Agent Inventory And Attack-Path Graph
|
|
555
|
+
|
|
556
|
+
The inventory layer is local-first and synthetic. It imports agent, identity, tool, provider, resource, side-effect, credential, and policy records with provenance and freshness into `.ghostapi/inventory.json`, and it never reaches out to a source. ROI and removal numbers come from imported counters and the local store only.
|
|
557
|
+
|
|
558
|
+
```ts
|
|
559
|
+
import {
|
|
560
|
+
createLocalInventoryController,
|
|
561
|
+
createTestInventoryOperatorAuthorizer,
|
|
562
|
+
} from "@yiaany/ghostapi";
|
|
563
|
+
|
|
564
|
+
const { authorizer, issue } = createTestInventoryOperatorAuthorizer();
|
|
565
|
+
const operator = issue({
|
|
566
|
+
id: "invop",
|
|
567
|
+
principalId: "invop-one",
|
|
568
|
+
tenantId: "tenant-a",
|
|
569
|
+
permissions: [
|
|
570
|
+
"inventory.import",
|
|
571
|
+
"inventory.inspect",
|
|
572
|
+
"inventory.analyze",
|
|
573
|
+
"inventory.remediate",
|
|
574
|
+
"inventory.export",
|
|
575
|
+
],
|
|
576
|
+
});
|
|
577
|
+
const controller = createLocalInventoryController({
|
|
578
|
+
operatorAuthorizer: authorizer,
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
await controller.import(operator, {
|
|
582
|
+
schemaVersion: 1,
|
|
583
|
+
kind: "ghostapi.inventory-import",
|
|
584
|
+
source: {
|
|
585
|
+
sourceId: "repo-config",
|
|
586
|
+
sourceType: "config",
|
|
587
|
+
sourceName: "Repo config",
|
|
588
|
+
},
|
|
589
|
+
agents: [
|
|
590
|
+
{
|
|
591
|
+
agentId: "agent-order",
|
|
592
|
+
name: "Order assistant",
|
|
593
|
+
identityIds: ["identity-order"],
|
|
594
|
+
environmentIds: ["production"],
|
|
595
|
+
gatewayManaged: true,
|
|
596
|
+
killSwitchEnabled: true,
|
|
597
|
+
},
|
|
598
|
+
],
|
|
599
|
+
identities: [
|
|
600
|
+
{
|
|
601
|
+
identityId: "identity-order",
|
|
602
|
+
principalId: "svc-order",
|
|
603
|
+
role: "service_account",
|
|
604
|
+
toolIds: ["tool-stripe"],
|
|
605
|
+
environmentIds: ["production"],
|
|
606
|
+
scopes: ["read", "charge"],
|
|
607
|
+
},
|
|
608
|
+
],
|
|
609
|
+
});
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
Imports carry a digest; re-importing the same payload refreshes freshness without duplicating records or edges. Every graph edge has provenance (`source`, `importedAt`, `importedBy`) and a freshness status.
|
|
613
|
+
|
|
614
|
+
```ts
|
|
615
|
+
const snapshot = await controller.inspect(operator); // tenant-scoped records, edges, findings
|
|
616
|
+
const paths = await controller.attackPaths(operator, "agent-order");
|
|
617
|
+
const blast = await controller.blastRadius(operator, "agent-order"); // heuristic-labeled, advisory
|
|
618
|
+
const { findings } = await controller.analyze(operator); // detections on the current store
|
|
619
|
+
const exported = await controller.export(operator); // inventory, policies, evidence, ROI
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
Detections cover orphaned agents, stale/unused credentials, excessive permissions, unowned production integrations, agents outside the gateway, missing kill switches, missing evidence, and policy drift. Heuristic findings are explicitly labeled; coverage gaps are surfaced as risk, not ignored.
|
|
623
|
+
|
|
624
|
+
Remediations are proposed against a finding and applied locally: assign an owner, reduce credential scopes (never expanded — proposals that add or keep scopes are rejected), revoke a credential, onboard an agent through the gateway, or create an eval scenario reference.
|
|
625
|
+
|
|
626
|
+
```ts
|
|
627
|
+
const excessive = snapshot.findings.find(
|
|
628
|
+
(finding) => finding.kind === "excessive_permissions",
|
|
629
|
+
);
|
|
630
|
+
const proposal = await controller.proposeRemediation(operator, {
|
|
631
|
+
findingId: excessive!.findingId,
|
|
632
|
+
kind: "reduce_scope",
|
|
633
|
+
targetKind: "credential",
|
|
634
|
+
targetId: "cred-stripe",
|
|
635
|
+
rationale: "Drop the unused admin scope.",
|
|
636
|
+
reducedScopes: ["read", "charge"],
|
|
637
|
+
});
|
|
638
|
+
await controller.applyRemediation(operator, proposal.remediationId);
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
The ROI report uses only imported counters and applied remediations; unmeasured values are `null` and listed in `notMeasured`. See the [inventory threat model](security/inventory-threat-model.md). The entry gate for enterprise numbers (a real pilot) is not yet met; results are for local synthetic review.
|
|
642
|
+
|
|
643
|
+
## Local Data And Retention
|
|
644
|
+
|
|
645
|
+
Runtime files default to `.ghostapi/`. Set `GHOSTAPI_DATA_DIR` to isolate tests or multiple instances. Persisted events use a 5 MiB active log plus two rotated archives, and each persisted event is capped at 256 KiB. Local JSON mutations use inter-process lock files and atomic replacement on the local filesystem.
|
|
646
|
+
|
|
647
|
+
Generated Vitest files read `GHOSTAPI_BASE_URL`, falling back to `http://127.0.0.1:8080`, so CI can use an ephemeral or custom port.
|
|
648
|
+
|
|
649
|
+
## Uninstall And Cleanup
|
|
650
|
+
|
|
651
|
+
Remove a global install with:
|
|
652
|
+
|
|
653
|
+
```bash
|
|
654
|
+
npm uninstall -g @yiaany/ghostapi
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
Remove local runtime state with `rm -rf .ghostapi` on POSIX shells, or delete the `.ghostapi` directory with PowerShell/Explorer on Windows. If `init` created setup files, remove `ghostapi.policy.yaml`, generated MCP snippets, and generated agent instructions only after reviewing local edits.
|
|
658
|
+
|
|
659
|
+
## Provider Capabilities
|
|
660
|
+
|
|
661
|
+
Inspect built-in providers and their implementation mode from the CLI:
|
|
662
|
+
|
|
663
|
+
```bash
|
|
664
|
+
ghostapi providers list
|
|
665
|
+
ghostapi providers inspect resend
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
The dashboard reads the same versioned capability manifests from:
|
|
669
|
+
|
|
670
|
+
```text
|
|
671
|
+
GET http://127.0.0.1:8080/api/providers
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
Resend is the first provider migrated to the `ProviderPack` contract. Its deterministic responses include `x-ghostapi-provider-pack` and `x-ghostapi-api-version`. Select the current GhostAPI compatibility version explicitly with `x-ghostapi-api-version: v1`. Other providers remain available through legacy adapters while they are migrated one at a time. Generic REST remains the fallback.
|
|
675
|
+
|
|
676
|
+
## Failure Scenarios
|
|
677
|
+
|
|
678
|
+
Use MCP or the dashboard to force deterministic responses such as Stripe card declines, rate limits, upstream errors, and latency. `set_api_behavior` supports a bounded optional `delayMs` value (0-10,000) for a specific method/path, allowing a repeatable client-timeout test without any provider call.
|
|
679
|
+
|
|
680
|
+
The goal is to make failure handling repeatable instead of relying on live provider behavior.
|