claude-code: write the agent's home as its own account where it has one
On the control node agents are to run as an account that cannot become root (hq ADR 0266). The runtime keeps running as the operator, so the token, the identity and the home-scope items go to the agent's home through a helper started as that account; the licence and the account file follow the move, what was placed in the operator's home goes once the agent holds the licence, and the guard refuses every tool of a session running as anyone else, recording to the journal. An API key is taken only from the agent's home and only when it is one, here and in the licence manager, so the tool cannot carry off the runtime's credential. Unchanged where no agent account is named.
This commit is contained in:
@@ -640,6 +640,10 @@ func (m *Manager) Release(ctx context.Context, consumer, by string) (map[string]
|
||||
|
||||
var licenceName = regexp.MustCompile(`^[A-Za-z0-9@._-]+$`)
|
||||
|
||||
// apiKeyShape is what an Anthropic API key looks like. A key of another shape is refused before it is kept,
|
||||
// so a file that is not a key (a credential, a private key) is never adopted as one (novox/hq ADR 0266).
|
||||
var apiKeyShape = regexp.MustCompile(`^sk-ant-[A-Za-z0-9_-]{20,300}$`)
|
||||
|
||||
// AdoptKey adopts an API key from a file on this node — never an argument (design 39 §6).
|
||||
func (m *Manager) AdoptKey(ctx context.Context, name, file string) (map[string]any, error) {
|
||||
raw, err := os.ReadFile(file)
|
||||
@@ -666,6 +670,10 @@ func (m *Manager) adoptKey(ctx context.Context, name, key, from string) (map[str
|
||||
if key == "" {
|
||||
return nil, errors.New("the key is empty")
|
||||
}
|
||||
if !apiKeyShape.MatchString(key) {
|
||||
// Never the content: what was handed may be a secret of another kind.
|
||||
return nil, errors.New("what was handed over is not an Anthropic API key (sk-ant-…): nothing adopted")
|
||||
}
|
||||
held, err := m.Store.Licence(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -230,7 +230,7 @@ func TestARotationAndASwitchEachGiveANewerGeneration(t *testing.T) {
|
||||
t.Fatalf("a rotation went from %d to %d", g0, g1)
|
||||
}
|
||||
file := filepath.Join(t.TempDir(), "key")
|
||||
_ = os.WriteFile(file, []byte("sk-ant-api-key\n"), 0o600)
|
||||
_ = os.WriteFile(file, []byte("sk-ant-api03-key-0123456789abcdefghij\n"), 0o600)
|
||||
if _, err := mm.m.AdoptKey(ctx, "api", file); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -403,13 +403,13 @@ func TestALoginThatDoesNotRefreshMovesNothing(t *testing.T) {
|
||||
func TestAnAPIKeySealedByANodeIsAdopted(t *testing.T) {
|
||||
mm := newMesh(t)
|
||||
ctx := context.Background()
|
||||
box, _ := Seal("sk-ant-api03-test\n", mm.keys.PublicKey)
|
||||
box, _ := Seal("sk-ant-api03-test-0123456789abcdefghij\n", mm.keys.PublicKey)
|
||||
r, err := mm.m.AdoptSealedKey(ctx, "api", box, "laptop")
|
||||
if err != nil || r["adopted"] != true {
|
||||
t.Fatalf("%v %v", r, err)
|
||||
}
|
||||
l, _ := mm.store.Licence(ctx, "api")
|
||||
if plain, _ := mm.m.Crypt.Open(l.Sealed); plain != "sk-ant-api03-test" {
|
||||
if plain, _ := mm.m.Crypt.Open(l.Sealed); plain != "sk-ant-api03-test-0123456789abcdefghij" {
|
||||
t.Fatalf("stored %q", plain)
|
||||
}
|
||||
other, _ := GenerateKeyPair()
|
||||
@@ -438,3 +438,19 @@ func TestARebuiltStoreNeverGivesAGenerationANodeHasPassed(t *testing.T) {
|
||||
t.Fatalf("the laptop applied generation 16 and was given %d", g)
|
||||
}
|
||||
}
|
||||
|
||||
// What is not an API key is never adopted as one, and the refusal never repeats it (novox/hq ADR 0266).
|
||||
func TestWhatIsNotAnAPIKeyIsRefused(t *testing.T) {
|
||||
mm := newMesh(t)
|
||||
ctx := context.Background()
|
||||
for _, notAKey := range []string{"-----BEGIN OPENSSH PRIVATE KEY-----\nabc", "SUAOPERATORNKEYSEED", "sk-ant-short"} {
|
||||
box, _ := Seal(notAKey, mm.keys.PublicKey)
|
||||
_, err := mm.m.AdoptSealedKey(ctx, "api", box, "laptop")
|
||||
if err == nil || strings.Contains(err.Error(), notAKey) {
|
||||
t.Fatalf("%q: refused without repeating it, got %v", notAKey, err)
|
||||
}
|
||||
}
|
||||
if l, _ := mm.store.Licence(ctx, "api"); l != nil {
|
||||
t.Fatal("nothing was kept")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user