feat(cli-credentials): add agent-scoped git credentials

This commit is contained in:
Goon committed 2026-05-31 16:49:23 +07:00
1 parent e6242dbbbc
commit 994acfe3a4
47 files changed
+2152 -113

No files matched your search

+50 -14
View File
@@ -381,8 +381,6 @@ func (s *SQLiteSecureCLIStore) LookupByBinary(ctx context.Context, binaryName st
var args []any
query := `SELECT ` + selectCols
// Project user-credential columns (encrypted_env + credential_type + host_scope).
// When userID is empty we cannot reference uc_user.* — emit NULL placeholders
// so the scan column count stays stable.
@@ -390,10 +388,17 @@ func (s *SQLiteSecureCLIStore) LookupByBinary(ctx context.Context, binaryName st
hasUserJoinNoAgent := userID != "" && agentID == nil
if hasUserJoin || hasUserJoinNoAgent {
query += `, uc_user.encrypted_env AS user_env, uc_user.credential_type AS user_cred_type, uc_user.host_scope AS user_host_scope FROM secure_cli_binaries b`
selectCols += `, uc_user.encrypted_env AS user_env, uc_user.credential_type AS user_cred_type, uc_user.host_scope AS user_host_scope`
} else {
query += `, NULL AS user_env, NULL AS user_cred_type, NULL AS user_host_scope FROM secure_cli_binaries b`
selectCols += `, NULL AS user_env, NULL AS user_cred_type, NULL AS user_host_scope`
}
if agentID != nil {
selectCols += `, ac.encrypted_env AS agent_env, ac.credential_type AS agent_cred_type, ac.host_scope AS agent_host_scope`
} else {
selectCols += `, NULL AS agent_env, NULL AS agent_cred_type, NULL AS agent_host_scope`
}
query := `SELECT ` + selectCols + ` FROM secure_cli_binaries b`
// LEFT JOIN agent grant
if agentID != nil {
@@ -402,6 +407,14 @@ func (s *SQLiteSecureCLIStore) LookupByBinary(ctx context.Context, binaryName st
} else {
query += ` LEFT JOIN secure_cli_agent_grants g ON 0`
}
if agentID != nil {
query += ` LEFT JOIN secure_cli_agent_credentials ac ON ac.binary_id = b.id AND ac.agent_id = ?`
args = append(args, *agentID)
if !isCross {
query += ` AND ac.tenant_id = ?`
args = append(args, tid)
}
}
// LEFT JOIN user credentials (only when we project uc_user.*)
if hasUserJoin || hasUserJoinNoAgent {
@@ -457,6 +470,8 @@ func (s *SQLiteSecureCLIStore) scanRowWithGrantAndUserEnv(row *sql.Row) (*store.
var grantEncEnv []byte
var userEnv []byte
var userCredType, userHostScope *string
var agentEnv []byte
var agentCredType, agentHostScope *string
var createdAt, updatedAt sqliteTime
err := row.Scan(
@@ -466,6 +481,7 @@ func (s *SQLiteSecureCLIStore) scanRowWithGrantAndUserEnv(row *sql.Row) (*store.
&b.Enabled, &b.CreatedBy, &b.AdapterName, &createdAt, &updatedAt,
&grantDenyArgs, &grantDenyVerbose, &grantTimeout, &grantTips, &grantEnabled, &grantID, &grantEncEnv,
&userEnv, &userCredType, &userHostScope,
&agentEnv, &agentCredType, &agentHostScope,
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
@@ -506,24 +522,46 @@ func (s *SQLiteSecureCLIStore) scanRowWithGrantAndUserEnv(row *sql.Row) (*store.
}
grant.TimeoutSeconds = grantTimeout
grant.Tips = grantTips
if len(grantEncEnv) > 0 && s.encKey != "" {
if decrypted, err := crypto.Decrypt(string(grantEncEnv), s.encKey); err == nil {
grant.EncryptedEnv = []byte(decrypted)
if len(grantEncEnv) > 0 {
if s.encKey != "" {
if decrypted, err := crypto.Decrypt(string(grantEncEnv), s.encKey); err == nil {
grant.EncryptedEnv = []byte(decrypted)
}
} else {
grant.EncryptedEnv = grantEncEnv
}
}
b.MergeGrantOverrides(grant)
}
// Decrypt per-user env
if len(userEnv) > 0 && s.encKey != "" {
if decrypted, err := crypto.Decrypt(string(userEnv), s.encKey); err == nil {
b.UserEnv = []byte(decrypted)
if len(userEnv) > 0 {
if s.encKey != "" {
if decrypted, err := crypto.Decrypt(string(userEnv), s.encKey); err == nil {
b.UserEnv = []byte(decrypted)
}
} else {
b.UserEnv = userEnv
}
}
// Typed-credential metadata from the joined user-credential row.
b.UserCredentialType = userCredType
b.UserHostScope = userHostScope
if len(b.UserEnv) > 0 || userCredType != nil || userHostScope != nil {
b.SetEffectiveCredential(b.UserEnv, userCredType, userHostScope, "user", "")
}
if len(agentEnv) > 0 || agentCredType != nil || agentHostScope != nil {
decrypted := agentEnv
if len(agentEnv) > 0 && s.encKey != "" {
if d, err := crypto.Decrypt(string(agentEnv), s.encKey); err == nil {
decrypted = []byte(d)
}
}
if b.CredentialSource == "" {
b.SetEffectiveCredential(decrypted, agentCredType, agentHostScope, "agent", "")
}
}
return &b, nil
}
@@ -533,7 +571,7 @@ func (s *SQLiteSecureCLIStore) applyContextSecureCLI(ctx context.Context, b *sto
if len(scopes) == 0 || b == nil {
return b, nil
}
hasUserCredential := len(b.UserEnv) > 0 || b.UserCredentialType != nil || b.UserHostScope != nil
hasUserCredential := b.CredentialSource == "user" || len(b.UserEnv) > 0 || b.UserCredentialType != nil || b.UserHostScope != nil
disabledByContext := false
for _, scope := range scopes {
grants, err := s.ListContextGrantsForScope(ctx, scope)
@@ -572,9 +610,7 @@ func (s *SQLiteSecureCLIStore) applyContextSecureCLI(ctx context.Context, b *sto
continue
}
if !hasUserCredential {
b.UserEnv = creds.EncryptedEnv
b.UserCredentialType = creds.CredentialType
b.UserHostScope = creds.HostScope
b.SetEffectiveCredential(creds.EncryptedEnv, creds.CredentialType, creds.HostScope, "context", scope.ScopeType+":"+scope.ScopeKey)
} else {
b.EncryptedEnv = creds.EncryptedEnv
}