
Maple Audit
- 6 installs
- 1.6k repo stars
- Updated August 5, 2026
- makisuo/maple
Helps with security tasks.
About
maple-audit is a Claude Code skill for security. It helps solo builders move faster with AI-assisted coding.
- maple-audit
- Security
- AI-coding skill
Maple Audit by the numbers
- 6 all-time installs (skills.sh)
- Ranked #1,706 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/makisuo/maple --skill maple-auditAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 6 |
|---|---|
| repo stars | ★ 1.6k |
| Last updated | August 5, 2026 |
| Repository | makisuo/maple ↗ |
What it does
Helps with security tasks.
Files
Maple instrumentation audit
Review an existing OpenTelemetry setup against what Maple actually consumes, produce a findings report, then fix the gaps. This is the counterpart to maple-onboard: that skill installs telemetry from scratch; this one assumes instrumentation exists and asks whether it's right.
Before auditing, read checks.md in this skill's directory — it is the full check registry (check ids, severities, what each gap breaks in Maple). Every finding you report must cite a check id from there; never invent attribute keys or conventions that aren't in checks.md or the upstream OTel semconv.
For how to fix what you find, use the companion skills — don't improvise recipes:
maple-onboarding-stylefor general OTel taste, VCS attributes, log bridges, metrics, LLM conventions.maple-nextjs-style,maple-nodejs-style,maple-python-style,maple-effect-style,maple-go-style,maple-rust-style,maple-java-style,maple-csharp-style,maple-kotlin-stylefor stack-specific bootstrap shapes.
Severity
| Severity | Meaning |
|---|---|
critical | Breaks a Maple feature outright or is a data risk: missing service.name, hand-stamped status strings (error analytics read zero rows), logs without trace correlation, missing peer.service/db.system on client spans, PII in attributes. |
warn | Feature works degraded or work is invisible: missing environment/VCS resource attrs, deprecated semconv keys, double-emission, high-cardinality labels or span names, outbound calls left Internal, untraced operations (missing auto-instrumentation for a used framework/driver, business operations and background jobs with no spans). |
info | Signal-quality improvements: trace-context propagation across async boundaries, span noise, missing metrics, naming style, missing gen_ai.* on LLM calls. |
Step 1 — Enumerate services and classify
Map every app/service in the repo exactly as maple-onboard Step 1 does (workspace manifests, apps/*, services/*, mobile, edge/serverless functions — skip pure type/config packages). For each, find its OTel bootstrap and classify:
- instrumented — has an SDK bootstrap; gets the full audit below.
- partial — some signals wired (e.g. traces but no log bridge); audit what exists, flag what's absent.
- not instrumented — no OTel at all. That is a single
criticalfinding pointing the user atmaple-onboard; don't run per-check audits against nothing.
Show the user the service list with classifications before auditing, so they can correct it.
Step 2 — Static code audit
Per instrumented service, work through the categories in checks.md:
1. *Bootstrap & resource (RES-\) — read the SDK init: is `service.name` explicit, are `service.version`, `deployment.environment.name`, `vcs.repository.url.full`, `vcs.ref.head.revision` on the resource? Any invented keys? 2. Status & kind (STAT-\) — grep for status handling: SDK status enum vs hand-stamped strings; failure paths recording exceptions; outbound calls created as `Client`/`Producer` spans. 3. Trace coverage (SPAN-\)* — what should be traced but isn't. Compare the dependency manifest against the instrumentation registered in the bootstrap (a DB driver or queue client with no instrumentation package means that whole category emits nothing). Then read the code for critical business operations, background jobs, and queue consumers with no span in their call path — when this service misbehaves, what would an operator need to see that currently emits nothing? 4. Service-map attribution (MAP-\) — for every outbound dependency in the code (HTTP clients to internal services, DB drivers, queues), check the corresponding spans set `peer.service` (and `db.system` for DBs) consistently. 5. Attribute keys (REN-\, NAME-\) — grep attribute call sites (`setAttribute`, `set_attribute`, `setAttributes`, attribute map literals) for the deprecated/camelCase keys in the REN table and for double-emission of old+new keys. 6. Logs (LOG-\) — is an OTLP log bridge wired under the logger the app actually uses? Do in-span logs carry trace context? Structured fields? 7. Metrics (MET-\) — instruments at module scope, low-cardinality labels, business coverage. 8. PII (PII-01) — scan attribute values for emails, tokens, auth headers, full bodies. 9. LLM (LLM-\)* — only if the project calls LLM providers.
Collect findings as you go: service, check id, severity, evidence (file:line), and which Maple feature it affects (from checks.md).
Step 3 — Live cross-check (only when the Maple MCP is connected)
If mcp__maple__* tools are available, verify the static findings against what's actually arriving. If they're not available, say so in one line of the report and move on — the static audit stands alone. Don't ask the user to install the MCP mid-audit.
- `get_instrumentation_recommendations` — the authoritative list of deprecated/double-emitted/non-conforming attribute keys, reconciled against the org's live span data, plus resource-attribute coverage gaps. Where it disagrees with your static REN/NAME findings, the live data wins (the code you read may not be deployed, or other emitters exist).
- `list_services` — confirm every service you enumerated actually reports. A service that's instrumented in code but absent here is a
criticalexport problem (bootstrap not loaded, key wrong, exporter blocked). - `explore_attributes` (resource scope) — confirm environment/VCS/version resource attrs arrive in practice.
- `service_map` — a service whose code makes outbound calls but shows no outgoing edges confirms MAP-01/02/03 findings from the data side.
- `get_service_top_operations` (or
search_tracesper service) — confirm trace-coverage findings: a critical operation you flagged under SPAN-02/03 that never appears as a span name in live data is confirmed untraced; a DB-heavy service whose traces contain no DB client spans confirms SPAN-01.
Annotate existing findings with live evidence rather than duplicating them.
Step 4 — Findings report
Present the report before making any edit. Format:
## Instrumentation audit — <repo>
### <service-name> (instrumented | partial | not instrumented)
| Severity | Check | Finding | Evidence | Affects |
| critical | MAP-01 | Client spans to billing lack peer.service | src/billing.ts:88 | service map edge missing |
| warn | REN-02 | emits http.status_code | live: 12.4k spans/24h | rename to http.response.status_code |
...
### Summary
N critical · N warn · N info across M services. Recommended fix order: …Every row carries a check id and concrete evidence (file:line for static, counts/tool output for live). If the MCP wasn't connected, note "live cross-check skipped — Maple MCP not connected" here.
Step 5 — Apply fixes
After the report, fix in severity order (critical → warn → info), following the relevant style skill for each edit. Rules:
- Pure key renames (REN rows of kind rename) have two valid routes: rename at the SDK (preferred — fixes it at the source), or accept the matching Recommendation Issue in Maple Settings → Ingestion, which creates an ingest attribute mapping. Tell the user both; default to the SDK rename when you're already editing that file.
- Double-emission and naming issues can only be fixed at the SDK — an ingest mapping can't merge keys.
- Idempotent edits; preserve the project's package manager, logger, and formatting.
- Never remove an existing observability vendor (Sentry, Datadog, …) — coexistence is the default.
- Never put PII in attributes; when fixing PII-01, replace the value with an ID or drop the attribute, don't hash-and-keep.
- Never commit, push, or open PRs.
If the user only wants the review, stop after Step 4 — the report is a complete deliverable.
Step 6 — Re-verify
For every service you touched, run the same smoke as maple-onboard Step 4: the service's own dev/build command starts cleanly, and OTLP POSTs from the running process return 2xx. A fix that breaks startup is a regression — fix or revert it, don't paper over it.
Then close out: summarize what changed per service, and tell the user that after deploying, re-running get_instrumentation_recommendations (or checking Settings → Ingestion in Maple) will show issues auto-resolving as the offending keys stop arriving — reconciliation is automatic.
Hard rules
- No edits before the findings report has been shown (Step 4).
- Every finding cites a check id from
checks.md; no invented attribute keys or conventions. - The static audit must work standalone — the Maple MCP is an enhancer, never a requirement.
- Never modify files outside the project root; never commit or push.
- Fix recipes come from the
maple-*-styleskills, not from memory.
Maple instrumentation audit checklist
The full check registry for the maple-audit skill. Each check has a stable id, what to look for in the code, a severity, the Maple feature affected, and where to find the fix recipe.
Severities:
- critical — breaks a Maple feature outright or is a data risk. Fix first.
- warn — the feature works degraded or the data is harder to query than it should be.
- info — nice-to-have; improves signal quality but nothing is broken.
Fix pointers reference the companion skills: maple-onboarding-style (general), maple-nodejs-style, maple-nextjs-style, maple-python-style, maple-go-style, maple-rust-style, maple-java-style, maple-csharp-style, maple-kotlin-style, maple-effect-style.
RES — Resource attributes
Set once per process on the SDK resource, in the OTel bootstrap. Look for the resource construction (resourceFromAttributes(...) / Resource.create(...) in TS, Resource(attributes={...}) in Python, resource.NewWithAttributes(...) in Go, Resource::builder() in Rust, registerOTel({ attributes }) for @vercel/otel).
| Id | Check | Severity | Feature affected |
|---|---|---|---|
| RES-01 | service.name is set explicitly per service (not the SDK default). | critical | Without it spans land in a synthetic unknown_service bucket — services list, map, alerts all blind. |
| RES-02 | service.version is set (release/semver or commit short-SHA). | warn | Per-version slices on service overview. |
| RES-03 | deployment.environment.name is set (the current semconv key). Maple also accepts the legacy deployment.environment; emitting either works, dual-emit is what Maple's SDKs do. | warn | Environment filtering/slicing everywhere (env = alias, per-env throughput/latency/error rate). |
| RES-04 | vcs.repository.url.full is set to the canonical https repo URL (not SSH, not a local path). | warn | Links telemetry back to source; repo context in error debugging. |
| RES-05 | vcs.ref.head.revision is set best-effort from build/runtime env (VERCEL_GIT_COMMIT_SHA, GITHUB_SHA, RAILWAY_GIT_COMMIT_SHA, …). Never shell out to git at runtime. | warn | Release markers and per-deploy metrics. Skipping the SHA is fine; skipping the URL (RES-04) is not. |
| RES-06 | No invented parallel keys: git.repo, app.repo_url, deployment.commit_sha, env, environment as resource keys. Use the semconv keys from RES-03/04/05 exactly. | warn | Invented keys don't power any Maple feature; the data exists but nothing reads it. |
| RES-07 | service.instance.id set when the service runs multiple replicas. | info | Distinguishing replicas in trace queries. |
| RES-08 | Cloud/platform attrs (cloud.provider, cloud.platform, cloud.region, process.runtime.name) present where the SDK's auto-detectors don't already supply them. Keep process.runtime.name values consistent across services (nodejs everywhere, not nodejs + node). | info | Platform badge + runtime icon on the service map. |
Fix: maple-onboarding-style → "VCS resource attributes" + the per-language style skill's bootstrap section.
STAT — Span status & kind
These are span fields, not attributes, and both spellings are load-bearing on the Maple side: stored values must be Title Case (Ok / Error / Unset, Server / Client / Producer / Consumer / Internal).
| Id | Check | Severity | Feature affected |
|---|---|---|---|
| STAT-01 | Status is set via the SDK's status API (span.setStatus({ code: SpanStatusCode.ERROR }), span.set_status(StatusCode.ERROR), span.SetStatus(codes.Error, …)) — never a hand-stamped string attribute or a hand-built OTLP value like "ERROR"/"error". | critical | Error analytics filter WHERE StatusCode = 'Error' — wrong casing silently yields zero errors everywhere. |
| STAT-02 | Failure paths actually set Error status + span.recordException(err) (or language equivalent) before rethrowing. A span that swallows errors as Unset hides them. | critical | Error rate, error issues, error-debug context. |
| STAT-03 | Outbound network calls (HTTP clients, DB drivers, queue producers) are Client/Producer kind spans — not Internal. Auto-instrumentation usually gets this right; hand-rolled spans often don't. | warn | The service map only draws edges for Client/Producer spans; an Internal call is invisible in the map. |
| STAT-04 | Inbound request handlers produce Server (or Consumer) spans. | warn | Route rendering, throughput attribution, Apdex. |
Fix: maple-onboarding-style + per-language style skill ("record exceptions" pattern; in TS/JS withSpan from @maple/otel-helpers handles status + exception recording).
SPAN — Trace coverage
Not just "are the spans right" but "is everything that should be traced actually traced". Read the code the way an on-call operator would: when this service misbehaves, which operations would they need to see? Auto-instrumentation covers HTTP in/out, DB queries, and framework lifecycle — that's the floor; anything with real latency or a failure mode above that floor needs its own span (the same bar maple-onboard Step 3 applies when installing).
| Id | Check | Severity | Feature affected |
|---|---|---|---|
| SPAN-01 | Auto-instrumentation is registered for every framework and client library the service actually uses — HTTP server, HTTP client, DB driver, queue client. A used dependency with no instrumentation package/hook means that whole traffic category emits nothing. | warn | Entire categories of work (e.g. all DB queries, all outbound HTTP) invisible in traces, latency breakdowns, and the service map. |
| SPAN-02 | Critical business operations are wrapped in spans: payment/order/signup flows, batch jobs, LLM calls, anything a human would ask "how long did it take and did it fail?" about. Span names are domain.verb with entity-ID attributes (order.id, tenant.id) and branch outcomes. | warn | The trace shows one opaque parent span; no per-operation latency, error attribution, or top-operations view for the work that matters. |
| SPAN-03 | Background jobs, cron tasks, and queue consumers start their own root spans (or continue propagated context) — they don't run untraced just because no inbound HTTP request started a trace. | warn | Async work is completely absent from Maple; failures in jobs surface nowhere. |
| SPAN-04 | Trace context propagates across async boundaries the code crosses: queue publish→consume carries context (or links), fire-and-forget tasks inherit the active span, internal service calls send traceparent. | info | Traces fragment at the boundary — the consumer's work appears as a disconnected trace instead of one end-to-end view. |
| SPAN-05 | No span noise at the other extreme: trivial getters, pure transforms, and per-item spans inside tight loops don't each get a span. | info | Trace waterfalls become unreadable; span volume (and cost) inflates without signal. |
Static heuristics for SPAN-01/02: compare the dependency manifest (HTTP frameworks, DB drivers, queue clients, LLM SDKs) against the instrumentation registered in the bootstrap; grep entry points for job/consumer handlers with no startActiveSpan/withSpan/decorator in their call path.
Fix: maple-onboard Step 3 + maple-onboarding-style (span naming, attributes, withSpan); per-language style skill for the auto-instrumentation package list.
MAP — Service-map attribution
| Id | Check | Severity | Feature affected |
|---|---|---|---|
| MAP-01 | Every Client/Producer span calling another internal service sets peer.service, and its value exactly matches the target's service.name. | critical | No peer.service (or peer.service on a Server span) ⇒ no edge drawn; the call path is invisible. |
| MAP-02 | DB client spans set db.system (e.g. postgresql, redis, clickhouse) and peer.service on the same span — both are required for a database node. | critical | No DB node on the map; DB calls render as generic external HTTP. |
| MAP-03 | peer.service spelling is consistent across all emitting services (tinybird, not tinybird + Tinybird + tb). Pick one canonical name per peer. | warn | Inconsistent names fragment one logical dependency into multiple map nodes. |
| MAP-04 | Queue/RPC dependencies set messaging.system + messaging.destination.name (queues) or rpc.system + rpc.service (RPC). | warn | External-dependency edges and the External Dependencies tab. |
Fix: maple-onboarding-style; the public reference is the "Service map" section of Maple's attributes docs (https://maple.dev/docs/concepts/otel-conventions).
REN — Deprecated / non-conforming attribute keys
<!-- This table mirrors ATTRIBUTE_RENAMES in packages/domain/src/recommendations.ts (the engine behind Maple's in-product Recommendation Issues). When the Maple MCP is connected, the get_instrumentation_recommendations tool is AUTHORITATIVE — it reconciles against live span data. This static table only powers the code-side audit. -->
Maple keeps fallback chains for the common legacy HTTP keys (legacy and current names feed the same fast columns), so these are renames for forward-compatibility and clean querying — warn, not breakage. Search the codebase for the deprecated key in setAttribute/set_attribute/attribute-map literals.
| Id | Deprecated key | Canonical key |
|---|---|---|
| REN-01 | http.method | http.request.method |
| REN-02 | http.status_code | http.response.status_code |
| REN-03 | http.url | url.full |
| REN-04 | http.target | url.path |
| REN-05 | http.scheme | url.scheme |
| REN-06 | http.host | server.address |
| REN-07 | http.flavor | network.protocol.version |
| REN-08 | http.request_content_length | http.request.body.size |
| REN-09 | http.response_content_length | http.response.body.size |
| REN-10 | http.user_agent | user_agent.original |
| REN-11 | net.peer.name | server.address |
| REN-12 | net.peer.port | server.port |
| REN-13 | net.host.name | server.address |
| REN-14 | net.host.port | server.port |
| REN-15 | net.sock.peer.addr | network.peer.address |
| REN-16 | net.sock.peer.port | network.peer.port |
| REN-17 | net.transport | network.transport |
| REN-18 | net.protocol.name | network.protocol.name |
| REN-19 | net.protocol.version | network.protocol.version |
| REN-20 | db.statement | db.query.text |
| REN-21 | db.operation | db.operation.name |
| REN-22 | db.system | db.system.name |
| REN-23 | db.name | db.namespace |
| REN-24 | db.sql.table | db.collection.name |
| REN-25 | messaging.destination | messaging.destination.name |
| REN-26 | messaging.url | server.address |
| REN-27 | messaging.protocol | network.protocol.name |
| REN-28 | messaging.protocol_version | network.protocol.version |
| REN-29 | faas.execution | faas.invocation_id |
| REN-30 | enduser.id | user.id |
| REN-31 | httpMethod | http.request.method |
| REN-32 | httpStatusCode | http.response.status_code |
| REN-33 | httpUrl | url.full |
| REN-34 | httpRoute | http.route |
| REN-35 | dbStatement | db.query.text |
| REN-36 | dbOperation | db.operation.name |
| REN-37 | userId | user.id |
All REN checks are warn. Two extra rules:
- REN-DUAL (warn): if a service emits both the deprecated and the canonical key on the same spans (double-emission), standardize on the canonical key at the SDK. An ingest mapping cannot merge them — the canonical key already exists, so the mapping would silently no-op.
- Fix routes: pure renames can be fixed either at the SDK (preferred) or by accepting the corresponding Recommendation Issue in Maple Settings → Ingestion (creates an ingest attribute mapping). Double-emission and naming issues can only be fixed at the SDK.
LOG — Logs
| Id | Check | Severity | Feature affected |
|---|---|---|---|
| LOG-01 | An OTLP log bridge/handler is wired under the app's existing logger (OTel LoggerProvider + the language's bridge: logs SDK + winston/pino transport, Python LoggingHandler, Rust tracing layer). Stdout-only logging never reaches Maple. | critical | Logs don't exist in Maple at all — no log search, no trace-correlated logs. |
| LOG-02 | Logs emitted inside an active span carry trace_id/span_id (most bridges inject automatically — verify the bridge sits on the path the app actually logs through). | critical | Log↔trace correlation: jumping from a trace to its logs and back. |
| LOG-03 | Logs are structured — fields like order_id, user_id as attributes, not interpolated into the message string. | warn | Attribute chips, filtering, log pattern mining. |
| LOG-04 | Severity uses the standard levels (TRACE/DEBUG/INFO/WARN/ERROR/FATAL). Error-level logs are reserved for unrecoverable conditions needing intervention. | warn | Severity row coloring; ERROR/FATAL drive the error banner. |
Fix: maple-onboarding-style "Signals" + per-language style skill's log-bridge section.
MET — Metrics
| Id | Check | Severity | Feature affected |
|---|---|---|---|
| MET-01 | Metric labels are low-cardinality — no user IDs, order IDs, request IDs, raw URLs as dimensions (tenant-like bounded IDs are fine if the repo already treats them that way). | warn | High-cardinality labels explode series counts and slow every metrics query. |
| MET-02 | Business state transitions have counters (created/completed/failed/retried) and key operations have latency histograms. | info | Dashboards and alerts on business health, not just HTTP plumbing. |
| MET-03 | Meters/instruments are created once at module scope, not per call. | info | Avoids churn and duplicate instrument registration. |
Fix: maple-onboarding-style "Signals" / "Metrics" sections.
NAME — Naming conventions
| Id | Check | Severity | Feature affected |
|---|---|---|---|
| NAME-01 | Attribute keys are lowercase dotted.snake_case and namespaced — no camelCase, no bare un-namespaced keys (covered keys: see REN-31…37). | info | Un-namespaced keys score lowest in the chip strip; inconsistent keys fragment queries. |
| NAME-02 | Span names are low-cardinality domain.verb (order.process, payment.charge) — never IDs or full URLs in the span name. | warn | High-cardinality span names break operation grouping and top-operations views. |
| NAME-03 | No maple_*-prefixed attributes — that namespace is reserved for Maple platform internals and hidden from the UI. | warn | Attributes silently hidden from chips. |
PII — Sensitive data
| Id | Check | Severity | Feature affected |
|---|---|---|---|
| PII-01 | No emails, passwords, tokens, API keys, auth headers, or full request/response bodies in span/log attributes. Grep attribute call sites for these value sources. | critical | Data risk — telemetry is broadly visible and retained. |
LLM — gen_ai conventions
Only applies when the project calls an LLM provider (OpenAI, Anthropic, Google, …).
| Id | Check | Severity | Feature affected |
|---|---|---|---|
| LLM-01 | LLM calls are observed by provider instrumentation (OpenInference packages) or carry gen_ai.* attributes (gen_ai.provider.name, gen_ai.request.model, token usage). | info | LLM usage/cost views; Maple computes estimated cost from these centrally. |
| LLM-02 | No app-side pricing tables or llm.cost_usd-style cost math; no duplicate token counters where provider instrumentation already captures usage; no invented parallel llm.* keys. | info | Cost belongs in Maple's central pricing layer; duplicates skew totals. |
Fix: maple-onboarding-style "LLM metrics".