Skip to content

feat: add oneclaw module for 1Claw MCP integration - #857

Merged
DevelopmentCats merged 10 commits into
coder:mainfrom
kmjones1979:add-kmjones1979-oneclaw-module
Aug 12, 2026
Merged

feat: add oneclaw module for 1Claw MCP integration#857
DevelopmentCats merged 10 commits into
coder:mainfrom
kmjones1979:add-kmjones1979-oneclaw-module

Conversation

@kmjones1979

Copy link
Copy Markdown
Contributor

Summary

Resubmission of #845 (closed for structural reasons) with the reviewer feedback addressed and additional security hardening of the bootstrap flow.

Adds the kmjones1979 namespace and the oneclaw module, which provides vault-backed secrets and MCP server wiring for AI coding agents (Cursor, Claude Code) in Coder workspaces.

Changes since #845

Structural (addresses @DevelopmentCats review)

The reviewer asked that the module follow the standard schema used in the coder/ namespace:

Generally in almost all cases you would just have: main.tf, README.md, main.test.ts, main.tftest.hcl and script files which amount to maybe one or two scripts.

Done:

  • variables.tf and outputs.tf are consolidated into main.tf.
  • scripts/bootstrap.sh and scripts/setup.sh are merged into a single scripts/run.sh executed by one coder_script.
  • The Terraform-native provisioning mode (scripts/provision.sh, null_resource.provision, master_api_key) is removed. That mode relied on a local-exec provisioner writing a state file to the provisioner's cwd, which is ephemeral inside Coder template provisioners and cannot round-trip credentials into coder_env. Two modes remain: bootstrap (recommended) and manual.

Final tree:

registry/kmjones1979/
├── README.md
├── .images/avatar.png
└── modules/oneclaw/
    ├── README.md
    ├── main.tf
    ├── main.test.ts
    ├── main.tftest.hcl
    └── scripts/run.sh

Security hardening for the 1ck_ human bootstrap key

The 1ck_ human API key is privileged (can create and destroy vaults in the caller's 1Claw account), so the module goes out of its way to make sure it cannot be recovered from the workspace after bootstrap:

  1. The key is delivered to the script as a sensitive coder_env variable (_ONECLAW_HUMAN_API_KEY) rather than via templatefile() substitution. As a result, the literal key never appears in the rendered script body that lives in Terraform state or in the Coder agent's /tmp/coder-agent.log. The rendered script only shows HUMAN_KEY="\${_ONECLAW_HUMAN_API_KEY:-}".
  2. The key is sent to the 1Claw auth endpoint via curl --data-binary @- from stdin, so it never appears in ps aux / /proc/<pid>/cmdline.
  3. HUMAN_KEY and _ONECLAW_HUMAN_API_KEY are unset immediately after authentication, so downstream subprocesses spawned by the script do not inherit the key.
  4. Only the scoped ocv_ agent key and the vault id are persisted to ~/.1claw/bootstrap.json and the MCP config files.
  5. README.md documents a post-bootstrap cleanup flow: once the state file exists, the user is instructed to set human_api_key = "" in their Terraform so subsequent restarts do not reference the human key at all.

Test plan

Verified against a local Coder server (v2.31.9) running the module with real 1Claw credentials.

  • terraform test passes (4 runs, Terraform 1.14 via Docker)
  • bun test main.test.ts passes (5 tests, including an explicit assertion that the human key value is not embedded in the rendered script)
  • shellcheck is clean on scripts/run.sh
  • bun run fmt leaves the tree unchanged
  • Live workspace on local Coder: first boot creates vault + agent + policy and writes bootstrap.json + Cursor/Claude MCP configs
  • Live workspace restart is idempotent — the script detects the state file and skips provisioning
  • Post-bootstrap cleanup flow (human_api_key = "", coder update, restart) continues to work using cached credentials
  • Filesystem audit after each scenario confirms the 1ck_ key value does not appear in any file on the workspace (state file, MCP configs, agent log, script log, shell init files, /proc/<pid>/environ of any Coder process)

Made with Cursor

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new community namespace (kmjones1979) and a new Terraform module (oneclaw) to configure 1Claw vault-backed secrets access and write/merge MCP server configuration for AI agents in Coder workspaces, including an optional “bootstrap” flow that provisions 1Claw resources from inside the workspace.

Changes:

  • Introduces registry/kmjones1979/ contributor namespace metadata (README + avatar reference).
  • Adds registry/kmjones1979/modules/oneclaw/ with Terraform module wiring (coder_env + coder_script) and a unified scripts/run.sh.
  • Adds Terraform (main.tftest.hcl) and Bun (main.test.ts) tests covering manual vs bootstrap behavior and ensuring the human key is not embedded in the rendered script.

Reviewed changes

Copilot reviewed 6 out of 8 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
registry/kmjones1979/README.md Adds namespace/frontmatter metadata for the new community contributor.
registry/kmjones1979/modules/oneclaw/README.md Documents module usage (bootstrap/manual) and the security model/cleanup steps.
registry/kmjones1979/modules/oneclaw/main.tf Implements the module’s inputs/outputs and Coder resources for env injection and startup script execution.
registry/kmjones1979/modules/oneclaw/main.tftest.hcl Adds Terraform plan-based assertions for the module’s two operating modes and env/script wiring.
registry/kmjones1979/modules/oneclaw/main.test.ts Adds integration-style tests validating rendered script behavior (including no human key literal).
registry/kmjones1979/modules/oneclaw/scripts/run.sh Implements the bootstrap provisioning flow plus MCP config write/merge logic.

Comment thread registry/kmjones1979/modules/oneclaw/scripts/run.sh Outdated
Comment thread registry/kmjones1979/modules/oneclaw/scripts/run.sh Outdated
Comment thread registry/kmjones1979/modules/oneclaw/scripts/run.sh Outdated
Comment thread registry/kmjones1979/modules/oneclaw/scripts/run.sh
Comment thread registry/kmjones1979/modules/oneclaw/main.tf Outdated
Comment thread registry/kmjones1979/modules/oneclaw/main.tf Outdated
Comment thread registry/kmjones1979/modules/oneclaw/scripts/run.sh Outdated
kmjones1979 added a commit to kmjones1979/registry that referenced this pull request Jun 21, 2026
main.tf:
- Remove unused `order` variable. coder_script does not support an order
  field, so the variable was dead code.
- Remove unused `data "coder_workspace_owner" "me"` source.

scripts/run.sh:
- Replace `eval echo` (used to expand `$HOME` / `~` in user-overridable
  config paths) with a dedicated expand_path() helper that uses bash
  string substitution and a case statement. Adds unit-test coverage
  confirming that injected `$(...)` and backticks are not executed.
- Rewrite the existing-vault lookup so the vault name is passed to
  Python via argv instead of being interpolated into the inline Python
  source. The JSON payload is now fed to Python on stdin via a
  here-string instead of via a pipe + here-doc combination, which
  shellcheck flagged (SC2259) as broken (the heredoc would have
  overridden the piped stdin and the program would have failed to
  parse the response).
- `unset _ONECLAW_HUMAN_API_KEY` immediately after reading it into
  HUMAN_KEY at the top of the script, so the privileged bootstrap
  key is no longer visible in /proc/<pid>/environ for the lifetime
  of the script run.
- Update the "no API token or vault ID" warning to drop the mention
  of `master_api_key`, which was removed earlier in this PR.

Made-with: Cursor
@kmjones1979

Copy link
Copy Markdown
Contributor Author

Thanks for the careful review — addressed all seven items in 4d5b414.

Copilot comment File Fix
eval echo on STATE_DIR enables command substitution scripts/run.sh:17 Replaced with expand_path() helper that does pure bash string substitution (${p//\$HOME/$HOME} and a case for ~/~/...). Unit-tested locally with '/tmp/$(touch …).cfg' and backtick payloads to confirm nothing executes.
eval echo on user-provided config paths scripts/run.sh:164 Same expand_path() helper.
Vault name interpolated into inline Python scripts/run.sh:108-114 Vault name is now passed via argv. While I was there I also fixed an unrelated bug shellcheck caught (SC2259): the rewrite originally used python3 - "$NAME" <<'PYEOF' which would have overridden the piped JSON with the heredoc; the final form uses python3 -c '...' "$NAME" <<<"$list_response" so argv carries the name, stdin carries the JSON, and the Python source is never interpolated.
Stale master_api_key reference in warning scripts/run.sh:214-216 Updated to "Provide api_token + vault_id (manual mode), or set human_api_key (bootstrap mode)" since the master-key path was removed earlier in this PR.
order variable declared but unused main.tf:118-122 Removed. coder_script doesn't expose an order field, so it was dead surface.
data "coder_workspace_owner" "me" unused main.tf:126 Removed.
_ONECLAW_HUMAN_API_KEY lingered in /proc/<pid>/environ for the whole run scripts/run.sh:83-86 Moved unset _ONECLAW_HUMAN_API_KEY to immediately after HUMAN_KEY="${_ONECLAW_HUMAN_API_KEY:-}" at the top of the script (line 39 now). The shell variable HUMAN_KEY is still scrubbed after auth completes, and the belt-and-suspenders unset of HUMAN_KEY after the bootstrap call is kept for the state-file short-circuit path.

All checks still green: terraform test (4 runs), bun test (5 tests), shellcheck, bun run fmt.

@kmjones1979
kmjones1979 force-pushed the add-kmjones1979-oneclaw-module branch from 4d5b414 to 69ea479 Compare June 25, 2026 13:53
kmjones1979 added a commit to kmjones1979/registry that referenced this pull request Jun 25, 2026
main.tf:
- Remove unused `order` variable. coder_script does not support an order
  field, so the variable was dead code.
- Remove unused `data "coder_workspace_owner" "me"` source.

scripts/run.sh:
- Replace `eval echo` (used to expand `$HOME` / `~` in user-overridable
  config paths) with a dedicated expand_path() helper that uses bash
  string substitution and a case statement. Adds unit-test coverage
  confirming that injected `$(...)` and backticks are not executed.
- Rewrite the existing-vault lookup so the vault name is passed to
  Python via argv instead of being interpolated into the inline Python
  source. The JSON payload is now fed to Python on stdin via a
  here-string instead of via a pipe + here-doc combination, which
  shellcheck flagged (SC2259) as broken (the heredoc would have
  overridden the piped stdin and the program would have failed to
  parse the response).
- `unset _ONECLAW_HUMAN_API_KEY` immediately after reading it into
  HUMAN_KEY at the top of the script, so the privileged bootstrap
  key is no longer visible in /proc/<pid>/environ for the lifetime
  of the script run.
- Update the "no API token or vault ID" warning to drop the mention
  of `master_api_key`, which was removed earlier in this PR.

Made-with: Cursor
@kmjones1979

Copy link
Copy Markdown
Contributor Author

I think all issues were fixed, tested this full flow locally and all works well. Let me know if you find anything else. Thanks for all the support on this PR.

Add kmjones1979 namespace and oneclaw module, ported from
1clawAI/1claw-coder-workspace-module. Provides vault-backed secrets
and MCP server config for AI coding agents in Coder workspaces.

- Namespace: kmjones1979 (avatar from GitHub)
- Module: oneclaw with three provisioning modes (terraform-native,
  shell bootstrap, manual)
- Tests: main.tftest.hcl (5 runs) and main.test.ts (5 tests)
- Scripts: provision.sh, bootstrap.sh, setup.sh

Made-with: Cursor
…dling

Addresses reviewer feedback on closed PR coder#845 that the module was "split up
way more than usual" and did not follow the registry module schema.

Structure (matches the coder/ namespace conventions):
- Collapse variables.tf + outputs.tf into main.tf
- Merge scripts/bootstrap.sh + scripts/setup.sh into a single scripts/run.sh
  executed by a single coder_script
- Remove Terraform-native provisioning mode (scripts/provision.sh,
  null_resource.provision, master_api_key): it relied on local-exec writing a
  state file to the provisioner's cwd, which is ephemeral inside Coder template
  provisioners and therefore cannot round-trip credentials into coder_env
- Keep two supported modes: bootstrap (human 1ck_ key, recommended) and
  manual (pre-provisioned scoped ocv_ key)

Security hardening for the 1ck_ human bootstrap key:
- Deliver the key via a sensitive coder_env (_ONECLAW_HUMAN_API_KEY) instead
  of templatefile() substitution, so the literal key never appears in the
  rendered script body stored in Terraform state or logged to the workspace's
  /tmp/coder-agent.log
- Send the key to the 1Claw auth endpoint via curl --data-binary @- from stdin
  so it does not appear in process argv (ps/proc/cmdline)
- Unset HUMAN_KEY and _ONECLAW_HUMAN_API_KEY as soon as auth completes so
  downstream processes do not inherit the key
- Only the scoped ocv_ agent key and vault id are persisted to
  ~/.1claw/bootstrap.json and the MCP config files
- README documents post-bootstrap cleanup (set human_api_key = "" once the
  state file exists) and the full security guarantees

Tested end-to-end against a local Coder server with real 1Claw credentials:
first boot, idempotent restart, and post-bootstrap cleanup all succeed and
leave no copy of the 1ck_ value anywhere on the workspace filesystem or in
its process environments.

Made-with: Cursor
main.tf:
- Remove unused `order` variable. coder_script does not support an order
  field, so the variable was dead code.
- Remove unused `data "coder_workspace_owner" "me"` source.

scripts/run.sh:
- Replace `eval echo` (used to expand `$HOME` / `~` in user-overridable
  config paths) with a dedicated expand_path() helper that uses bash
  string substitution and a case statement. Adds unit-test coverage
  confirming that injected `$(...)` and backticks are not executed.
- Rewrite the existing-vault lookup so the vault name is passed to
  Python via argv instead of being interpolated into the inline Python
  source. The JSON payload is now fed to Python on stdin via a
  here-string instead of via a pipe + here-doc combination, which
  shellcheck flagged (SC2259) as broken (the heredoc would have
  overridden the piped stdin and the program would have failed to
  parse the response).
- `unset _ONECLAW_HUMAN_API_KEY` immediately after reading it into
  HUMAN_KEY at the top of the script, so the privileged bootstrap
  key is no longer visible in /proc/<pid>/environ for the lifetime
  of the script run.
- Update the "no API token or vault ID" warning to drop the mention
  of `master_api_key`, which was removed earlier in this PR.

Made-with: Cursor
The repo's README validator (cmd/readmevalidation) requires every module
README to contain exactly one fenced ```tf code block (with a `version`
field) between the H1 and the next heading. The previous structure had
the H1 followed directly by `## Usage`, with the tf example nested
inside `### Bootstrap mode`, so the validator failed with:

  "registry/kmjones1979/modules/oneclaw/README.md":
  did not find Terraform code block within h1 section

Move the canonical bootstrap-mode example up to sit directly under the
H1 paragraph (matching the convention used by registry/coder/modules/*),
and drop the now-duplicate block from the Bootstrap mode subsection.

Made-with: Cursor
@kmjones1979
kmjones1979 force-pushed the add-kmjones1979-oneclaw-module branch from 69ea479 to aac4cd3 Compare July 8, 2026 00:29
kmjones1979 and others added 3 commits August 10, 2026 11:46
Merge latest registry main (including lowercase namespace validation) and
update the module icon plus README links to match current 1Claw MCP docs and
llms.txt guidance.

Co-authored-by: Cursor <cursoragent@cursor.com>
Swap the oversized embedded SVG for the current round dark PNG and update
module icon references accordingly.

Co-authored-by: Cursor <cursoragent@cursor.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (8)

registry/kmjones1979/modules/oneclaw/scripts/run.sh:150

  • This API call constructs JSON via string interpolation. If bootstrap_agent_name contains quotes/newlines, it can break the JSON payload. Building the request body via JSON serialization avoids escaping bugs.
  log "Creating agent '$AGENT_NAME_IN'..."
  local agent_response agent_id agent_key
  agent_response=$(api_call POST "/v1/agents" "$jwt" \
    "{\"name\": \"$AGENT_NAME_IN\", \"vault_ids\": [\"$vault\"]}") || die "Failed to create agent"

registry/kmjones1979/modules/oneclaw/scripts/run.sh:162

  • This API call constructs JSON via string interpolation. If bootstrap_policy_path contains quotes/newlines, it can break the JSON payload. Building the policy body via JSON serialization avoids escaping bugs.
  log "Creating access policy (path: $POLICY_PATH_IN)..."
  api_call POST "/v1/vaults/$vault/policies" "$jwt" \
    "{\"secret_path_pattern\": \"$POLICY_PATH_IN\", \"principal_type\": \"agent\", \"principal_id\": \"$agent_id\", \"permissions\": [\"read\", \"write\"]}" \
    > /dev/null || die "Failed to create policy"

registry/kmjones1979/modules/oneclaw/main.tf:170

  • This module introduces a new startup script via a raw coder_script resource. Per registry guidance for new/rewritten script-based modules, prefer using the coder-utils module to orchestrate scripts (ordering, sync, logs) and to standardize the runtime directory layout; see e.g. registry/matifali/modules/omnigent/main.tf:145-157 for the expected pattern.
resource "coder_script" "run" {
  agent_id           = var.agent_id
  display_name       = "1Claw"
  icon               = var.icon
  run_on_start       = true

registry/kmjones1979/modules/oneclaw/main.tf:181

  • STATE_DIR is set to $HOME/.1claw, which means the module writes its own runtime state directly under $HOME. The registry’s module data layout guidance expects module-controlled runtime data to live under $HOME/.coder-modules/<namespace>/<module-name>/... to keep workspace state discoverable and avoid ad-hoc dot-directories.
    POLICY_PATH           = var.bootstrap_policy_path
    STATE_DIR             = "$HOME/.1claw"
    MCP_HOST              = var.mcp_host

registry/kmjones1979/modules/oneclaw/scripts/run.sh:3

  • This script writes secrets (bootstrap state and MCP config containing bearer tokens) and only applies chmod 600 after writing. Adding umask 077 up-front prevents a brief window where newly created files may be readable under a permissive default umask.
#!/bin/bash
set -euo pipefail

registry/kmjones1979/modules/oneclaw/scripts/run.sh:75

  • api_call() tries to capture curl’s exit code, but with set -e + pipefail enabled, a failing curl in the command substitution can terminate the script before rc=$? is reached. This makes the function’s error handling unreliable (and can prevent intended fallbacks like “create vault, else list and find existing”).
  if [ -n "$body" ]; then
    response=$(printf '%s' "$body" | curl -s -w "\n%%{http_code}" \
      -K "$curl_cfg" \
      -H "Content-Type: application/json" \
      --data-binary @- \
      -X "$method" "$API_URL$path" 2>&1)

registry/kmjones1979/modules/oneclaw/scripts/run.sh:124

  • This API call constructs JSON via string interpolation. If bootstrap_vault_name contains quotes/newlines, it will produce invalid JSON (and is hard to debug). Consider generating the JSON body via a serializer (Python is already a dependency in this script) so values are properly escaped.

This issue also appears in the following locations of the same file:

  • line 147
  • line 159
    log "Creating vault '$VAULT_NAME_IN'..."
    local vault_response
    vault_response=$(api_call POST "/v1/vaults" "$jwt" \
      "{\"name\": \"$VAULT_NAME_IN\"}") || {

registry/kmjones1979/modules/oneclaw/main.tf:64

  • agent_id_1claw / ONECLAW_AGENT_ID is defined and exported, but it’s not referenced anywhere in scripts/run.sh nor included in the generated MCP config (which only sets Authorization + X-Vault-ID). Unless something else consumes this env var, it’s dead configuration and the variable description may mislead users.
variable "agent_id_1claw" {
  type        = string
  description = "Optional 1Claw agent UUID. When omitted, the MCP server resolves the agent from the API key prefix."
  default     = ""
}

Migrate script orchestration to coder-utils with the standard module data
directory, harden API request bodies and curl error handling, and update tests
and docs for the new install pipeline.

Co-authored-by: Cursor <cursoragent@cursor.com>
@kmjones1979

Copy link
Copy Markdown
Contributor Author

Addressed all 8 suppressed Copilot findings from the Aug 12 review in 0b7c957.

Copilot finding Fix
JSON bodies built via string interpolation (run.sh vault/agent/policy/auth) All API request bodies now serialized with Python json.dumps() helpers (json_vault_create, json_agent_create, json_policy_create, json_auth_body)
api_call() unreliable under set -e + pipefail Capture curl exit code with || rc=$? so fallbacks (e.g. vault create → list by name) work correctly
Missing umask 077 before writing secrets Added at top of install script
Runtime data under ~/.1claw Moved to $HOME/.coder-modules/kmjones1979/oneclaw/ (bootstrap.json, logs via coder-utils); legacy ~/.1claw/bootstrap.json auto-migrated on next start
Raw coder_script instead of coder-utils Migrated to module "coder_utils" with scripts/install.sh.tftpl; exposes output "scripts" for downstream sync
Dead agent_id_1claw config Manual mode still sets ONECLAW_AGENT_ID via coder_env; bootstrap mode loads agent ID from bootstrap.json and exports ONECLAW_AGENT_ID at runtime
(prior June review items) Still fixed: expand_path(), argv-safe Python, early _ONECLAW_HUMAN_API_KEY unset, no dead TF vars

Tests run locally:

  • terraform test — 4/4 pass (TF 1.14)
  • bun test main.test.ts — 5/5 pass
  • go run ./cmd/readmevalidation — pass
  • bun run fmt — clean

Note: Bootstrap no longer sets start_blocks_login (coder-utils does not expose it today; same pattern as claude-code). Bootstrap still runs on first workspace start via the install script.

@DevelopmentCats DevelopmentCats left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM I tested everything in my own workspaces as well and it works like a charm!

Thank you very much for the contribution!!

@DevelopmentCats
DevelopmentCats enabled auto-merge (squash) August 12, 2026 18:29
@DevelopmentCats
DevelopmentCats merged commit f586805 into coder:main Aug 12, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants