Skip to content

feat(phase-06): make secrets specific to installed claws, not global#16

Merged
ric03uec merged 15 commits intomainfrom
gsd/phase-06-secrets-per-claw
Mar 23, 2026
Merged

feat(phase-06): make secrets specific to installed claws, not global#16
ric03uec merged 15 commits intomainfrom
gsd/phase-06-secrets-per-claw

Conversation

@ric03uec
Copy link
Owner

Summary

  • Refactor secrets module from global to per-claw-instance scoping
  • Each installed claw instance now has isolated secrets using instance key format host:claw_type:claw_name
  • CLI commands updated to require claw_name as first positional argument
  • Fleet status shows DEGRADED state for claws with missing required secrets

Requirements Delivered

  • PSEC-01: Per-instance secret scoping
  • PSEC-02: Per-claw CLI set command
  • PSEC-03: Grouped list with missing secrets
  • PSEC-04: Degraded state display
  • PSEC-05: Validation for installed claws

Test plan

  • 287 tests passing
  • Linter passing
  • Instance key validation rejects colons (prevents namespace spoofing)
  • Per-claw secret set/get/remove operations
  • Fleet status shows degraded state with missing secret names

ATX Review Summary

Review 1: Rating 3/5

Blocking issues:

# Location Issue Recommendation Status
B1 secrets.py:180-191 get_instance_key() concatenates components with : without validation. Hostname containing : spoofs another instance's namespace. Validate components reject :. Reuse/extend KEY_ID_PATTERN. ✅ FIXED
B2 health.py:155 check_cmd builds shell command with f'pgrep -u {claw_user} node ...'. Shell injection risk if regex weakened. Add shlex.quote(claw_user) defense-in-depth. ✅ FIXED
B3 secrets.py (4 locations) Atomic save block copy-pasted 4 times. Bug fixes require 4 edits. Extract _save_secrets_locked() helper. ✅ FIXED
B4 get_missing_secrets() Zero direct tests for core Phase 06 logic. Fragile split('-', 1) parsing never exercised. Add unit tests for name derivation paths. ✅ FIXED
B5 tests/test_health.py:224-250 DEGRADED tests never validate instance_key argument passed to mocked get_instance_secrets. Capture call_args and assert instance key matches. ✅ FIXED

Warnings:

# Location Issue Recommendation
W1 health.py:57-62 Claw name derived via fragile heuristic. Silent mismatch returns empty missing-list. Pass claw_name explicitly from claw_record['name']. ✅ FIXED
W2 secrets.py:365-524 Deprecated global functions remain fully functional with no DeprecationWarning. Emit warning; schedule hard removal.
W3 secrets.py:145 save_secrets() exported but doesn't acquire lock internally. Make private or acquire lock internally.
W4-W9 Various Event ordering, error handling, test naming See full review

Review 2: Rating 3/5

Blocking issues:

# Location Issue Recommendation Status
B1 tests/test_cli_secret.py 12+ duplicate host-setup blocks. Any schema change requires 12+ edits. Extract hosts_with_one_claw fixture to conftest.py. ✅ FIXED
B2 tests/test_health.py:53 test_health_check_stopped doesn't assert result['missing_secrets'] is None. No guard against regressions. Add assertion or remove redundant test. ✅ FIXED

Warnings:

# Location Issue Recommendation
W1 secrets.py:129-133 Exception chaining may leak partial file contents in logs. Use from None to suppress chain.
W2 secrets.py:394-526 Deprecated functions still operational, no DeprecationWarning. Emit warning immediately.
W3-W9 Various Raw output in errors, rc check, schema validation See full review

Suggestions:

  • Replace time.sleep(0.1) with monkeypatched datetime
  • Standardize hint text styling
  • Add edge-case test for exactly 3 missing secrets
  • Document exit code semantics

Co-Authored-By: @atx-ci 269048218+atx-ci@users.noreply.github.com


🤖 Generated with Claude Code

Devashish and others added 15 commits March 22, 2026 15:09
Plan breakdown:
- 06-01: Refactor core secrets module for per-instance scoping
- 06-02: Update CLI commands for per-claw secrets
- 06-03: Update status display for degraded state

Requirements: PSEC-01 through PSEC-05

Decisions implemented:
- D-01 through D-06: Per-claw-instance secrets with host:claw_type:claw_name identity
- D-07 through D-09: Nested JSON storage structure
- D-10 through D-13: CLI command changes
- D-14 through D-17: Install flow changes (degraded state)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add tests for get_instance_key, get_instance_secrets, set_instance_secret
- Add tests for remove_instance_secret, list_instances_with_secrets
- Test same key different values per instance
- Update existing tests to use nested structure
- Remove deprecated global function tests

TDD RED phase - tests fail as expected
- Refactor load_secrets/save_secrets to use nested structure: {instance_key: {secret_key: SecretEntry}}
- Add get_instance_key(host, claw_type, claw_name) -> instance key formatter
- Add get_instance_secrets(instance_key) -> dict of secrets for instance
- Add set_instance_secret(instance_key, key, value, description) -> per-instance set
- Add remove_instance_secret(instance_key, key) -> per-instance remove
- Add list_instances_with_secrets() -> list of instance keys with secrets
- Add ClawNotFoundError exception class
- Keep legacy global functions (get_secret, set_secret, etc.) using __global__ namespace for CLI backward compatibility
- Update CLI secret list to read from __global__ namespace
- Update test assertions to check __global__ namespace
- File permissions remain 0o600
- Same locking and atomic write patterns

TDD GREEN phase - all tests pass

Addresses: PSEC-01, PSEC-05
- Add ClawStatus.DEGRADED enum value for running claws with missing secrets
- Add missing_secrets field to HealthResult TypedDict
- Implement get_missing_secrets() helper to check required secrets
- Update check_claw_health() to return DEGRADED when missing required secrets
- Update all return statements to include missing_secrets field
- Add comprehensive tests for degraded state and secret checking
- Running claws with all secrets show as RUNNING, not DEGRADED

Fixes PSEC-04 health check requirement.
- Modified set_cmd to require claw_name as first positional argument
- Added get_installed_claw() function to validate claw exists in hosts
- Updated set_cmd to use get_instance_key and set_instance_secret
- Validates claw exists before accepting secrets (raises ClawNotFoundError)
- Updated all existing set tests to use per-claw pattern with hosts
- Added new tests: test_secret_set_with_claw, test_secret_set_claw_not_found

Changes:
- src/clawrium/core/secrets.py: Added get_installed_claw() function
- src/clawrium/cli/secret.py: Updated set_cmd signature and implementation
- tests/test_cli_secret.py: Updated all set tests for per-claw pattern

All set tests passing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Import HealthResult to store full health check results
- Change health_results dict to store HealthResult instead of just ClawStatus
- Add DEGRADED status display with yellow color
- Show missing secret keys in degraded message (first 3, truncate rest)
- Update all existing tests to include missing_secrets field in mocks
- Add tests for degraded state display and truncation

Running claws with missing secrets now show:
  degraded (missing: KEY1, KEY2, KEY3)

Completes PSEC-04 status display requirement.
- Modified list_cmd to show secrets grouped by claw instance
- list_cmd iterates over hosts and their installed claws
- Shows missing required secrets per claw instance
- Shows "No claws installed" when hosts.json is empty
- Modified remove_cmd to require claw_name as first positional argument
- remove_cmd validates claw exists before removing secrets
- Updated all existing list and remove tests for per-claw pattern
- Added new tests: test_secret_list_grouped_by_claw,
  test_secret_list_shows_missing_required, test_secret_list_no_claws_installed,
  test_secret_remove_with_claw, test_secret_remove_claw_not_found

Changes:
- src/clawrium/cli/secret.py: Updated list_cmd and remove_cmd implementations
- tests/test_cli_secret.py: Updated all list/remove tests for per-claw pattern

All 273 tests passing. Linter passing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
B1: Validate instance key components reject colons to prevent
    namespace spoofing. Added INSTANCE_KEY_COMPONENT_PATTERN and
    InvalidInstanceKeyComponentError.

B2: Add shlex.quote() defense-in-depth for claw_user in shell
    command despite regex validation.

B3: Extract _save_secrets_atomic() helper to eliminate 4x
    duplicated atomic save blocks.

B4/B5: Add comprehensive tests for get_missing_secrets() with
    instance_key verification. Tests cover: name field priority,
    user fallback derivation, multi-hyphen names, no-hyphen
    fallback, empty/missing fields.

W1: Use claw_record['name'] directly instead of fragile
    derivation from claw_user field.

Tests: 287 passed (14 new tests added)

ATX Review Summary
Review 1: Rating 3/5
Blocking issues:
| # | Location | Issue | Status |
|---|----------|-------|--------|
| B1 | secrets.py:180-191 | Instance key injection via colon | FIXED |
| B2 | health.py:155 | Shell injection risk | FIXED |
| B3 | secrets.py (4 locations) | Duplicated save logic | FIXED |
| B4 | get_missing_secrets() | Zero tests for core logic | FIXED |
| B5 | test_health.py:224-250 | Instance key not verified | FIXED |

Warnings addressed:
| # | Issue | Status |
|---|-------|--------|
| W1 | Fragile claw name derivation | FIXED |

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: @atx-ci <269048218+atx-ci@users.noreply.github.com>
B1: Extract hosts_with_installed_claw fixture to conftest.py.
    Refactored test_cli_secret.py to use shared fixture, eliminating
    12+ duplicate host-setup blocks.

B2: Add missing_secrets assertion to test_health_check_stopped.
    STOPPED status should not check secrets (missing_secrets = None).

Tests: 287 passed

ATX Review Summary
Review 2: Rating 3/5
Blocking issues:
| # | Location | Issue | Status |
|---|----------|-------|--------|
| B1 | test_cli_secret.py | 12+ duplicate host-setup blocks | FIXED |
| B2 | test_health.py:53 | Missing assertion for missing_secrets | FIXED |

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: @atx-ci <269048218+atx-ci@users.noreply.github.com>
- Make secret list require claw_name argument (no global listing)
- Add SecretsFileCorruptedError handling to set_cmd and remove_cmd
- Expand get_installed_claw to search name, user, and claw_type fields
- Inject secrets as ansible vars during install
- Add systemd service creation and startup to zeroclaw/openclaw playbooks
- Add LLM config (provider_url, model, api_key) to zeroclaw playbook
- Declare LLM secrets in zeroclaw manifest
- Add tests for invalid key format, corrupted secrets, keyboard interrupt

ATX Review Summary
Review 1: Rating 3/5
Blocking issues fixed: B1-B4

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@ric03uec ric03uec merged commit 650d1cb into main Mar 23, 2026
1 check 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.

1 participant