Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/app/commands/create_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func (r *CreateClientResult) ToText() string {

// ToJSON returns a JSON representation of the creation result.
func (r *CreateClientResult) ToJSON() string {
// #nosec G117 - Intentionally marshaling secret for CLI output. This is shown once during client creation.
jsonBytes, _ := json.MarshalIndent(r, "", " ")
return string(jsonBytes)
}
Expand Down
5 changes: 5 additions & 0 deletions conductor/archive/validate_capabilities_20260306/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Track validate_capabilities_20260306 Context

- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"track_id": "validate_capabilities_20260306",
"type": "other",
"status": "new",
"created_at": "2026-03-06T10:00:00Z",
"updated_at": "2026-03-06T10:00:00Z",
"description": "Validate Capabilities in ParseCapabilities"
}
26 changes: 26 additions & 0 deletions conductor/archive/validate_capabilities_20260306/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Implementation Plan: Validate Capabilities in ParseCapabilities

## Overview
Enhance `ParseCapabilities` in `internal/ui` to strictly validate capabilities against the `internal/auth/domain` constants.

## Phase 1: Domain Enhancement [checkpoint: 39909a2]
Add validation helpers to the domain package to ensure extensibility.

- [x] Task: Add `ValidCapabilities()` and `IsValidCapability()` to `internal/auth/domain/const.go`. 6d24b0e
- [x] Task: Add unit tests for `IsValidCapability()` in `internal/auth/domain/const_test.go` (create file if needed). 6d24b0e
- [x] Task: Conductor - User Manual Verification 'Phase 1: Domain Enhancement' (Protocol in workflow.md)

## Phase 2: UI Implementation [checkpoint: 6ce42dd]
Update `ParseCapabilities` to use the domain validation.

- [x] Task: Update `internal/ui/policies_test.go` with failing test cases for invalid capabilities and case-sensitivity. d7770f6
- [x] Task: Update `internal/ui/policies.go`'s `ParseCapabilities` function to perform validation using the domain helpers. d7770f6
- [x] Task: Verify that `PromptForPolicies` correctly bubbles up these errors (existing tests or add new ones). d7770f6
- [x] Task: Conductor - User Manual Verification 'Phase 2: UI Implementation' (Protocol in workflow.md)

## Phase 3: Documentation and Quality Gates [checkpoint: 911c90f]
Ensure everything is consistent and meets quality standards.

- [x] Task: Verify `docs/auth/policies.md` and `docs/cli-commands.md` align with the strict validation (already appear to, but double check examples). 3bb2424
- [x] Task: Run full test suite (`make test`) and linting (`make lint`). 3bb2424
- [x] Task: Conductor - User Manual Verification 'Phase 3: Documentation and Quality Gates' (Protocol in workflow.md)
30 changes: 30 additions & 0 deletions conductor/archive/validate_capabilities_20260306/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Specification: Validate Capabilities in ParseCapabilities

## Overview
Currently, `ParseCapabilities` in the `internal/ui` package accepts any non-empty string as a valid capability. This track implements strict validation to ensure only predefined domain capabilities are accepted, aligning the CLI behavior with the documented security model.

## Functional Requirements
- **Strict Validation:** Each capability in the input string must match one of the valid capabilities defined in the `internal/auth/domain` package.
- **Error Handling:** If any single capability is invalid or unknown, the entire parsing operation must fail with a descriptive error.
- **Normalization:** Matching must be case-sensitive. Only exact, lowercase matches (as defined in the domain) are considered valid.
- **Extensible Implementation:**
- Add a `IsValidCapability(Capability) bool` or `ValidCapabilities() []Capability` helper to `internal/auth/domain`.
- `ParseCapabilities` should use this helper to perform validation.
- **Documentation Alignment:** Ensure that `docs/auth/policies.md` and any other relevant documentation accurately reflect these requirements (already appears aligned, but requires final verification).

## Domain Capabilities (as of v1.0.0)
- `read`
- `write`
- `delete`
- `encrypt`
- `decrypt`
- `rotate`

## Acceptance Criteria
- `ParseCapabilities("read,write")` succeeds and returns `[]authDomain.Capability{"read", "write"}`.
- `ParseCapabilities("read, invalid")` fails with an error indicating that "invalid" is not a valid capability.
- `ParseCapabilities("READ")` fails (strict case-sensitivity).
- `ParseCapabilities("")` and `ParseCapabilities(" , ")` continue to fail.
- Unit tests in `internal/ui/policies_test.go` are updated/added.
- New unit tests for domain helper are added.
- `PromptForPolicies` flow correctly handles these validation errors.
1 change: 1 addition & 0 deletions conductor/tech-stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- **Password Hashing:** [go-pwdhash](https://github.com/allisson/go-pwdhash) - Argon2id hashing for secure storage of client secrets and passwords.
- **Request Body Size Limiting:** Middleware to prevent DoS attacks from large payloads.
- **Secret Value Size Limiting:** Global limit on individual secret values to ensure predictable storage and memory usage.
- **Strict Capability Validation:** Centralized domain helpers for validating policy capabilities (`read`, `write`, `delete`, `encrypt`, `decrypt`, `rotate`) in CLI and API layers.
- **Secret Path Validation:** Strict naming rules for secret paths (alphanumeric, -, _, /) to ensure consistency and security.
- **Audit Signing:** HMAC-SHA256 for tamper-evident cryptographic audit logs.

Expand Down
2 changes: 2 additions & 0 deletions conductor/tracks.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Project Tracks

This file tracks all major tracks for the project. Each track has its own detailed plan in its respective folder.

---
24 changes: 24 additions & 0 deletions internal/auth/domain/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,27 @@ const (
// RotateCapability allows rotating cryptographic keys.
RotateCapability Capability = "rotate"
)

// ValidCapabilities returns a list of all defined capabilities in the system.
// Used for validation and UI generation.
func ValidCapabilities() []Capability {
return []Capability{
ReadCapability,
WriteCapability,
DeleteCapability,
EncryptCapability,
DecryptCapability,
RotateCapability,
}
}

// IsValidCapability checks if a capability is valid and exists in the system.
// Used for strict validation in policy parsing.
func IsValidCapability(cap Capability) bool {
for _, valid := range ValidCapabilities() {
if cap == valid {
return true
}
}
return false
}
42 changes: 42 additions & 0 deletions internal/auth/domain/const_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package domain

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestValidCapabilities(t *testing.T) {
caps := ValidCapabilities()
assert.Len(t, caps, 6)
assert.Contains(t, caps, ReadCapability)
assert.Contains(t, caps, WriteCapability)
assert.Contains(t, caps, DeleteCapability)
assert.Contains(t, caps, EncryptCapability)
assert.Contains(t, caps, DecryptCapability)
assert.Contains(t, caps, RotateCapability)
}

func TestIsValidCapability(t *testing.T) {
tests := []struct {
name string
cap Capability
expected bool
}{
{"read is valid", ReadCapability, true},
{"write is valid", WriteCapability, true},
{"delete is valid", DeleteCapability, true},
{"encrypt is valid", EncryptCapability, true},
{"decrypt is valid", DecryptCapability, true},
{"rotate is valid", RotateCapability, true},
{"invalid is not valid", Capability("invalid"), false},
{"empty is not valid", Capability(""), false},
{"case sensitive is not valid", Capability("READ"), false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, IsValidCapability(tt.cap))
})
}
}
1 change: 1 addition & 0 deletions internal/auth/http/token_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func TestTokenHandler_IssueTokenHandler(t *testing.T) {

w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
// #nosec G117 - Test code with hardcoded test fixtures, not real secrets
body, _ := json.Marshal(request)
c.Request, _ = http.NewRequest(http.MethodPost, "/v1/token", bytes.NewBuffer(body))

Expand Down
1 change: 1 addition & 0 deletions internal/tokenization/service/luhn_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func (g *luhnGenerator) Generate(length int) (string, error) {
// Convert to string
token := make([]byte, length)
for i, d := range digits {
// #nosec G115 - Safe conversion: d is guaranteed to be 0-9, so '0'+d produces ASCII 48-57 (well within byte range)
token[i] = byte('0' + d)
}

Expand Down
13 changes: 10 additions & 3 deletions internal/ui/policies.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,22 @@ func PromptForPoliciesUpdate(
}

// ParseCapabilities converts a comma-separated string into a slice of Capability.
// Performs strict validation against valid domain capabilities.
func ParseCapabilities(input string) ([]authDomain.Capability, error) {
parts := strings.Split(input, ",")
capabilities := make([]authDomain.Capability, 0, len(parts))

for _, part := range parts {
cap := authDomain.Capability(strings.TrimSpace(part))
if cap != "" {
capabilities = append(capabilities, cap)
trimmed := strings.TrimSpace(part)
if trimmed == "" {
continue
}

cap := authDomain.Capability(trimmed)
if !authDomain.IsValidCapability(cap) {
return nil, fmt.Errorf("invalid capability: '%s'", trimmed)
}
capabilities = append(capabilities, cap)
}

if len(capabilities) == 0 {
Expand Down
11 changes: 11 additions & 0 deletions internal/ui/policies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ func TestParseCapabilities(t *testing.T) {
}{
{"read,write", []authDomain.Capability{"read", "write"}, false},
{"read , write ", []authDomain.Capability{"read", "write"}, false},
{"read,invalid", nil, true},
{"READ", nil, true},
{"", nil, true},
{" , ", nil, true},
}
Expand Down Expand Up @@ -66,4 +68,13 @@ func TestPromptForPolicies(t *testing.T) {
require.Error(t, err)
require.Contains(t, err.Error(), "path cannot be empty")
})

t.Run("invalid-capability", func(t *testing.T) {
input := "secret/*\ninvalid\n"
var output bytes.Buffer
_, err := PromptForPolicies(strings.NewReader(input), &output)

require.Error(t, err)
require.Contains(t, err.Error(), "invalid capability: 'invalid'")
})
}
Loading