Skip to content
Back to skills

Moonbase

ASecurity

Optional Moonbase license-activation integration for Pulp — load-bearing compile settings, OpenSSL-at-configure caveat, the moonbase-pulp User-Agent contract, audio-thread gating + click-free fade, async start/pump, the interactive native (no-WebView) activation editor (frame-tick polling + the don't-rebuild-mid-event trap), loadable plugin/standalone formats, and headless screenshots.

  • 22 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 3, 2026
designpythonc++nodetestinggitbackendsecurity

Works with

  • cli

Security analysis

A100/100

Scanned September 3, 2026

npx -y skills add danielraffel/pulp --skill moonbase --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Moonbase?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Moonbase
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/danielraffel-moonbase/badge)](https://www.skillsdirectory.com/skills/danielraffel-moonbase)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: moonbase
description: Optional Moonbase license-activation integration for Pulp — load-bearing compile settings, OpenSSL-at-configure caveat, the moonbase-pulp User-Agent contract, audio-thread gating + click-free fade, async start/pump, the interactive native (no-WebView) activation editor (frame-tick polling + the don't-rebuild-mid-event trap), loadable plugin/standalone formats, and headless screenshots.
requires:
  scripts: []
  tools: []
---

# Moonbase Skill

Use this when adding, building, or testing license activation / copy protection
in a Pulp plugin or app with [Moonbase](https://github.com/Moonbase-sh/moonbase-cpp),
or when working under `examples/moonbase-activation/`.

Moonbase is a hosted licensing service. `moonbase-cpp` is a header-only,
MIT-licensed C++17 client. Pulp consumes it via FetchContent (target
`moonbase::licensing`) — no fork, no vendoring. Pulp owns only a registry entry,
docs, this skill, and the reference example; upstream owns the SDK.

## Load-bearing build settings (lead with these)

`pulp add moonbase` resolves the dependency and the link target, but it does
**not** carry the two cache settings below. Set them *before* the dependency is
made available:

```cmake
set(MOONBASE_BUILD_TESTS    OFF CACHE BOOL "" FORCE)
set(MOONBASE_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(MOONBASE_USE_CURL       OFF CACHE BOOL "" FORCE)  # supply your own transport

target_link_libraries(MyPlugin PRIVATE moonbase::licensing)
```

`MOONBASE_USE_CURL=OFF` makes the upstream INTERFACE target define
`MOONBASE_DISABLE_CURL_TRANSPORT` for you and drops libcurl. Inject a transport
instead — the example supplies one over Pulp's `cpp-httplib`/mbedTLS stack.

## OpenSSL bolt-on (known upstream caveat)

Moonbase documents OpenSSL as a system requirement (its README expects
`OpenSSL::SSL`/`OpenSSL::Crypto` findable on the system), and v3.3.0's CMake runs
`find_package(OpenSSL REQUIRED)` and links it **unconditionally**, with **no**
`MOONBASE_CRYPTO_NATIVE` CMake wiring. So OpenSSL must be present *at configure
time*, and v1 simply uses the OpenSSL crypto backend (required + linked anyway) —
the simplest, no-extra-wiring path. This is upstream's intended design, **not** a
blocker and **not** a reason to fork. The example's CMake auto-discovers OpenSSL;
per platform:

- **macOS:** `brew install openssl@3` (`/opt/homebrew/opt/openssl@3` on Apple
  silicon); pass `-DOPENSSL_ROOT_DIR=$(brew --prefix openssl@3)` if needed.
- **Linux:** `apt install libssl-dev` (or distro equivalent).
- **Windows:** vcpkg (`vcpkg install openssl`) or a system package, discoverable
  via the toolchain / `OPENSSL_ROOT_DIR`.

> **OS-native crypto is optional, not v1's default.** `MOONBASE_CRYPTO_NATIVE` is
> a preprocessor macro (define it on the consuming target *and* link the platform
> crypto libraries) that switches RS256 to Security.framework / CNG / system
> libcrypto. Since OpenSSL is a documented Moonbase requirement and is linked
> regardless, native crypto does **not** let you drop OpenSSL — so it buys little
> here. The example uses the OpenSSL backend; don't bother with native for v1.

## `moonbase-pulp` User-Agent contract

Set Moonbase's `client_info` so Moonbase can attribute Pulp-driven traffic:

```cpp
options.client_info = "moonbase-pulp/<version> (GenerousCorp Pulp; <OS>)";
```

It is appended after `moonbase-cpp/<ver>` in the HTTP `User-Agent`:

```
User-Agent: moonbase-cpp/3.3.0 moonbase-pulp/1.0.0 (GenerousCorp Pulp; macOS)
```

The `moonbase-pulp` token mirrors Moonbase's `moonbase-juce` convention, so
their client segmentation attributes Pulp traffic with no work on their side.
The example wires it through a single named constant; document it so even a
hand-rolled integration sets it. Recommend it, don't silently force it — it is
the developer's HTTP client and field.

## Audio-thread gating rule

The one hard real-time rule. The audio thread reads a single atomic; everything
else runs off-thread:

```cpp
// process() — audio thread, lock-free:
if (!licensed.load(std::memory_order_relaxed)) {
    buffer.clear();   // or bypass / reduced functionality
    return;
}
```

All Moonbase calls are non-realtime (`rt_safe: false`). Never call Moonbase,
allocate, or block from `process()`. A non-audio controller owns network,
storage, and revalidation and publishes only the `std::atomic<bool> licensed`.

## UI convention

- **Native by default, no WebView.** The activation UI renders through Pulp's
  view/canvas stack. Online activation opens the **system browser** and the
  editor polls from the native controller (matching Moonbase's own reference
  flow). An embedded WebView is an optional later mode, not the default.
- **Theme tokens, not a Moonbase palette.** Build the panel from
  `pulp::view::Theme` tokens so it inherits the host plugin's look. Moonbase
  supplies licensing behavior, not the editor's visual identity.
- **Two surfaces, one layout.** `build_activation_view()` is a STATIC panel for a
  `const` controller (tests + the headless screenshot tool); `ActivationPanel` is
  the INTERACTIVE editor whose button drives the controller and which rebuilds on
  screen change. Both share the `detail::lay_out_panel` helpers, so the rich
  license-details rendering (name/email/product/type/expiry/seats) stays in one
  place.

## Editor wiring (and the one reentrancy trap)

The live editor drives the controller from the view's **frame clock**
(`view.frame_clock()->subscribe([](float dt){...})`): each tick calls
`controller.pump()` (applies background revalidation), `poll_once()` ~1 s while in
`BrowserWait`, and `ActivationPanel::refresh_if_changed()` to rebuild when the
screen advances. Return `false` from the subscriber once the editor closes to
auto-unsubscribe.

> **Never rebuild the view tree from inside a child's event handler.** The action
> button's `on_click` must NOT call `rebuild()` — that destroys the very button
> whose click is still being dispatched (a use-after-free → SIGSEGV). Instead the
> handler only invokes the controller action + `request_repaint()`; the screen
> changed, so the next frame tick's `refresh_if_changed()` rebuilds safely once
> the event has unwound. This is why the action is deferred, not immediate.

## Non-blocking startup (start_async / pump)

`start_async()` applies any stored license SYNCHRONOUSLY (the editor opens
unlocked with no network wait) and re-validates online on a ONE-SHOT background
thread. The worker only writes a mutex-guarded result slot + an atomic ready flag;
`screen_`/`license_` are touched solely on the UI thread (`start_async`/`pump`),
so there is no data race on UI state and the audio thread still only reads
`licensed()`. The controller joins the worker in its destructor. This is the
HALO-style "synchronous-local-then-async-online" pattern — do not turn it into a
continuous background poller (that reintroduces the UI-state race).

## Click-free gate

Gate audio through a `pulp::signal::SmoothedValue<float>` (12 ms ramp set in
`prepare()`), not a hard mute: `gate_.set_target(licensed ? 1 : 0)` then multiply
each frame by `gate_.next()`. Before `prepare()` the ramp is one sample (an
instant switch), so a unit test that never prepares still sees hard gating.

## Loadable formats + screenshots

`pulp_add_plugin(MoonbaseActivation FORMATS CLAP Standalone ...)` builds a real
loadable plugin + app; VST3/AU are appended when their developer-supplied SDKs
are present. Each format target must also link `moonbase::licensing` +
`pulp-cpp-httplib` (loop over `MoonbaseActivation_<FMT>`). The
`moonbase-activation-screenshots` tool renders each screen headlessly via
`render_to_file(..., ScreenshotBackend::skia)` — faithful text needs Skia in the
build.

## Controller scope

Keep the Pulp controller a **thin router** over upstream semantics. It owns UI
state, background scheduling, generation-guarding of its own async callbacks,
and the audio-thread atomic. It does **not** re-implement Moonbase's
cross-process store locking, online-validation grace handling, or stale-write
protection — lean on upstream's `validate_token_online` and `file_license_store`
as-is.

## Validate before shipping

- Registry: `python3 tools/packages/validate_registry.py --check-licenses` — the
  `moonbase` entry is clean (license MIT, FetchContent, target
  `moonbase::licensing`).
- Deps: `python3 tools/deps/audit.py --strict` — Moonbase must show
  `DEPENDENCIES.md=yes NOTICE.md=yes licensing.md=yes`.
- Transport test: assert the `moonbase-pulp` token actually appears on the wire
  (method/URL/headers/body round-trip against a local server).
- Gating test: with `licensed=false`, `process()` fades to silence; flipped to
  true, audio fades back to pass-through (assert the ramp is monotonic + settles,
  not just the steady state).
- Interactive test: `simulate_click` the action button → controller advances
  (e.g. Welcome → BrowserWait) → `refresh_if_changed()` rebuilds; assert no
  WebView node after interaction.
- Async test: `start_async()` with a stored license sets `licensed()` + the
  Details screen immediately; `pump()` in a loop never drops the license.
- UI proof: render the activation screens through the Pulp view tree (Skia);
  assert no WebView node on the default path.
- Loadable build: `MoonbaseActivation_CLAP` / `_Standalone` link and produce
  bundles; the screenshot tool writes faithful PNGs.
- No live network in CI — live activation stays a manual local check.

## Pointers

- Reference plugin: `examples/moonbase-activation/`
- Developer guide: `docs/guides/copyright-protection.md`
- Registry entry: `tools/packages/registry.json` (`moonbase`)
- Upstream: https://github.com/Moonbase-sh/moonbase-cpp (pinned `v3.3.0`)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…