See every call, decision and change your app makes
Get a full picture of a MainSDK app - destinations, drivers, access decisions and a tamper-evident audit log - in your own OpenTelemetry stack or SIEM.
Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
You will see what your app does, from your own tools: which outside hosts it calls, which driver serves each interface, why each access request was allowed or denied, and who changed what. Everything runs inside your app and goes to the destinations you choose. Nothing is sent to MainSDK until you log in and turn it on.
You need
- A MainSDK app with a
main.yaml. - Optional: an OpenTelemetry collector or a SIEM that accepts OTLP.
1. Ask the app what it is
npx mainsdk info # human-readable
npx mainsdk info --json # for scripts, agents and reviewsIt lists the project, each interface and its driver, every network destination (yours and each driver's declared hosts), the folders, server facts and programs the code uses (see Declare what your app uses), tracking settings such as email.tracking: none, and every open exception with its owner and expiry. Run it in CI and diff it to catch a new destination or driver in a pull request.
A running app can serve a signed copy at /.well-known/mainsdk.json. mainsdk info https://app.example.com checks the signature, so a buyer can verify the record without access to your code.
2. Turn on the audit log
logging:
audit: true
redact: [personal, sensitive, child, phi]
retention: 12mEvery action and every policy decision is written as an OCSF record with the actor, the action, the resource, the decision and the policy version. Each record carries the hash of the one before it, so an edit, deletion or reorder is detectable:
import { log } from "mainsdk";
const result = log.verifyAudit({ from: "2026-10-01" });
if (!result.ok) throw new Error(`Audit log broken at ${result.brokenAt}`);The audit log is never sampled or dropped. If its sink is down, records buffer to local disk and MAIN-LOG-0010 is reported.
3. Find out why a request was denied
Every decision has an id. policy.explain returns the policy line that made it:
import { auth, policy } from "mainsdk";
export async function GET(req: Request) {
await auth.require(req, { role: "admin" });
const id = new URL(req.url).searchParams.get("decision") ?? "";
return Response.json(policy.explain(id));
}Blocked network calls are decisions too: each one names the destination, the rule and the calling file and line (see Restrict which hosts and IPs your app can reach).
4. Send it to your own tools
telemetry:
to: [terminal, otel]
otel:
endpoint: https://otel.acme.dev:4318
headers: { authorization: secret:OTEL_TOKEN }
sample: 0.2 # traces only; errors, decisions and audit are never sampledLogs follow the OpenTelemetry data model with W3C trace ids, so they land in Grafana, Datadog, Honeycomb or your SIEM next to the rest of your traces. Export is asynchronous and bounded: if the collector is down, telemetry is dropped and counted, and your requests never wait (ADR-0003).
What is kept out
Redaction runs in your app before anything is written or exported:
- Fields labelled
personal,sensitive,childorphi, anything resolved from asecret:reference, and known key formats. - Request and response bodies, cookies and
authorizationheaders, unless you turn them on. - User ids are pseudonymised per project, and IP addresses are trimmed (IPv4 to /24, IPv6 to /48).
The telemetry@1 contract lists every field, its purpose and its redaction rule. A field not in it is dropped (MAIN-LOG-0005).
Verify
npx mainsdk check --env prod
npx mainsdk info --jsoncheck passes with no MAIN-LOG finding, and info lists only the destinations and drivers you expect.
If it fails
| Code | Cause | Fix |
|---|---|---|
MAIN-LOG-0003 | MAINSDK_DEBUG=driver was ignored in production. | Use a debug session (phase 2). |
MAIN-LOG-0005 | An attribute not in telemetry@1 was dropped before export. | Remove it, or propose it for the contract. |
MAIN-LOG-0010 | The audit sink is unavailable; records are buffering to local disk. | Restore the sink. |
MAIN-INFO-0002 | The signed record from a running app doesn't verify. | Treat the record as untrusted and redeploy from a clean build. |
For agents
Goal: give this app an audit log and send its telemetry to the team's collector.
Read: docs/specs/log-telemetry.md, this page, main.yaml.
Touch only: main.yaml, main.env.prod.yaml.
Do: set logging.audit: true with redact labels; set telemetry.to: [terminal, otel] and otel.endpoint;
keep the collector token as a secret: reference.
Verify: npx mainsdk check --json --agent && npx mainsdk info --json
Never turn off redaction or log request bodies to debug.What comes later
- Streaming each customer organisation's audit log to its own SIEM (phase 2).
- One view across every app: a live destination map that alerts on any new host, and reachable-vulnerability reports, in Fleet (phase 4, spec
fleet-observe).
Next
- Declare what your app uses: the hosts, folders, facts and programs
infolists - Make your app enterprise ready
- Logs, audit and telemetry, the interface reference
Was this page helpful?