DEV Community

Rock
Rock

Posted on Fully Autonomous

Making a Bot Production-Ready: Operation Logs, 2FA and Signed Plugins

Scenario

Before a bot serves real users, an operator faces three questions: who changed the provider key yesterday and deleted that pipeline; who can log in, and is there a second door besides the password; and which signature is on the installed plugin archive and which code is running.

On 4.10 these were answered by grepping container logs and trusting one set of credentials. 4.11 adds the accountability layer: workspace-scoped operation logs, two-factor authentication (2FA) plus passkeys, and plugin certification with plugin signatures.

How it works in 4.11

Operation logs. 4.11 adds the workspace-scoped operation log table workspace_operation_logs (source LangBot/src/langbot/pkg/persistence/alembic/versions/0033_operation_traceability.py). Each row carries the workspace, actor name/role, auth type, route, action, resource type/id, outcome and status code, a changes before/after diff, dedupe_key, client IP and duration (entity LangBot/src/langbot/pkg/entity/persistence/operation_log.py:47).

Levels are off / mutation / read and default to off (LangBot/src/langbot/pkg/operation_trace/service.py:92). Each row's record_hash is an HMAC-SHA256 value chained to the previous row's prev_hash (service.py:200), so the panel can show "verified" vs "possibly tampered" and filter for hash mismatches or broken chains.

This audit surface is separate from the Runner's own event_log (entity LangBot/src/langbot/pkg/entity/persistence/event_log.py); the two are deliberately kept apart — event_log is not the source of this page.

2FA and passkeys. The account panel supports TOTP and WebAuthn passkeys. The TOTP secret is never stored in the clear: a Fernet token whose wrapping key is derived from the instance JWT secret via HKDF-SHA256 (LangBot/src/langbot/pkg/api/http/service/totp.py, salt langbot.totp.secret.v1); recovery codes are only PBKDF2-HMAC-SHA256 digests (600000 iterations). The login challenge lasts 5 minutes with at most 5 attempts, and the login route separates "second factor required" (totp_required) from "bad code" (totp_invalid_code) (LangBot/src/langbot/pkg/api/http/controller/groups/user.py:118-141). Passkeys use WebAuthn registration and authentication, with credentials in passkey_credentials (migration 0024_passkey_credentials.py). Workspace roles are fixed (owner / admin / developer / operator / viewer, LangBot/src/langbot/pkg/api/http/authz.py:42-63); the last owner cannot be deleted or demoted (docs/multi-tenant/workspace-multi-user-architecture.md §5.5).

Plugin signatures and certification. On install, Core calls the SDK's langbot_plugin.certification.verify_archive() before writing the artifact: a strict certificate envelope in the ZIP comment is verified against the signed normalized ZIP digest without extracting the payload (docs/architecture/certified-plugins.md). The outcome lands in the plugin record install_info._certification (source LangBot/src/langbot/pkg/plugin/connector.py:1896-1910). Only verification: valid with both shared-runtime-v1 and stateless-v1 can reach a shared runtime profile; OSS ships with an empty trusted key ring, so unresolvable declarations fall back to dedicated. administrator_force is honored only as the literal boolean true.

Measured on 2026-10-16: a 4.10.11 database at alembic_version = 0023_bot_scoped_sessions migrated automatically to 0037_metadata_text — 14 revisions, 50 tables — with passkeys (0024), TOTP (0025), certification digest backfill (0032) and operation logs (0033) in range.

Hands-on steps

Start a local demo instance:

# 4.11.0 (fresh instance)
docker network create lang7-411
docker run -d --name lang7-411-core --network lang7-411 --restart on-failure -e TZ=Asia/Shanghai \
  -e BOX__ENABLED=false -e SPACE__DISABLE_TELEMETRY=true -p 15400:5300 -p 15480-15485:2280-2285 \
  -v "$PWD/deploy/411/data:/app/data" rockchin/langbot:v4.11.0
docker run -d --name lang7-411-runtime --network lang7-411 --network-alias langbot_plugin_runtime --restart on-failure \
  -e TZ=Asia/Shanghai -e SPACE__DISABLE_TELEMETRY=true -v "$PWD/deploy/411/data/plugins:/app/data/plugins" \
  rockchin/langbot:v4.11.0 uv run --no-sync -m langbot_plugin.cli.__init__ rt
Enter fullscreen mode Exit fullscreen mode

The initial admin account is admin@langbot.local with password LangBot411!demo (a local demo value — replace it). Three places to visit:

  1. Operation logs: Settings → Operation logs. Set "Operation level" from off to mutation or read, adjust retention / max rows / dedupe window, and save. Then filter the record list by action, resource type and actor and expand "Details" for the change set.
  2. 2FA: Settings → Account → "Two-step verification". Click Enable → Generate secret, scan the QR code, enter the 6-digit code and confirm; recovery codes appear only at that confirm step (the UI copy is "Two-factor authentication is enabled · {{count}} recovery codes remaining"). The same panel offers "Add passkey". Owners and admins can also view and reset other accounts' 2FA.
  3. Plugin certification: marketplace versions with the "certified plugin" badge (gated on the marketplace-side certificate_status: "issued") are labelled as reviewed for data isolation and shared-runtime compatibility, with a signed archive. The instance-side admission fields — admission_code / verification / certificate_id / runtime_profile — have no WebUI surface at all (a search of web/src returns zero hits — the market badge is the only render), so read them from install_info._certification in GET /api/v1/plugins.

Operation log page: level and retention settings, plus the record list with change counts and integrity state

Account settings security block: passkeys (none bound on this instance) and two-step verification (shown as not enabled)

Verify

  • Operation logs: after raising the level to mutation, make one config change and GET /api/v1/settings/operation-logs should return that row. That route, plus .../filters and .../export, requires audit.view (source LangBot/src/langbot/pkg/operation_trace/routes.py:15); paging caps at 200 rows and export at 10000 (service.py:109, service.py:2293).
  • 2FA: after enrolling, log in again and you get the two-step challenge; a wrong code returns totp_invalid_code, and a recovery code takes the same path. Enrolment was not completed in this measurement, so this bullet comes from the UI copy and the source, not an observed result: GET /api/v1/user/totp/status stayed {"enabled": false, "pending": false, "confirmed_at": null, "recovery_codes_remaining": 0} throughout.
  • Plugin certification: the measured plugin came from the marketplace, and the two sides report its version differently (recorded 2026-10-16):
    • instance side install_info._certification (verbatim): {"admission_code": "CERTIFIED_PLUGIN_OSS_UNTRUSTED_DEDICATED", "artifact_digest": "2d2ce17055d70dd7736e6902595be3262992fd1598a00bd9d79d19fef1c16329", "certificate_component_model": "stateless-v1", "certificate_id": null, "certificate_runtime_profile": "shared-runtime-v1", "normalized_digest": "fc70e08968d8358a33e2cb3032bd1846b8acf38b40b4a7b5f66032c2e12e8205", "runtime_profile": "dedicated", "verification": "invalid"}
    • marketplace side, same version: the plugin record (GET https://space.langbot.app/api/v1/marketplace/plugins/langbot-team/LocalAgent) reports certification_status: "issued" and certificate_id: cert:86f25832a3f5df65b6e2f938155f3a855ed69ead87d835ddf54c49b4607b629f; the version record (same path plus /versions) adds certificate_key_id: ed25519:86e697952a6c3bc4.
    • How to read it: verification is the verdict on this instance, runtime_profile the profile actually selected, and a null certificate_id means no certificate ID was attached. Cross-checking the two sides is your job, and an issued marketplace badge must not be read as proof of Cloud admission.

The

Limits and caveats

  • Operation logs are off by default (DEFAULT_OPERATION_LEVEL = 0): nothing is recorded until you enable them; the store is append-only with retention as the only deletion path.
  • Operation logs cover admin actions inside the LangBot instance only — not the external platform side (WeChat, Lark, DingTalk and so on). Where a platform example appears: Support depends on the adapter and platform — check the support matrix first.
  • Roles are fixed RBAC: developer, operator and viewer never get audit.view, custom roles are not supported, and OSS is one workspace with multiple accounts — the architecture document still labels itself a baseline, not a ready-to-ship multi-tenant deployment.
  • Certification is an admission decision, not execution isolation. Runner plugins are operator-owned execution: LangBot protects its own resources with run-scoped authorization and a scoped MCP gateway using a short-lived run_token, while the execution environment is the user's or deployment platform's — no managed sandbox is provided (docs/agent-runner-pluginization/SECURITY_HARDENING.md).
  • These three mechanisms prepare for going live; they are not all that going live requires.

Summary and next steps

If your goal is to bring a 4.10 instance over and add this governance layer on top, start with the pinned migration document docs/pipeline-migration-config-map.zh-CN.md and the v4.11.0 release. Managed hosting is only a conditional secondary option at space.langbot.app; this article makes no promise about it.

Top comments (0)