[Backend] Add SECRET credential type for storing generic sensitive values (#4246)

This commit is contained in:
Marc Kelechava
2025-12-09 11:19:57 -08:00
committed by GitHub
parent 71e4614cfe
commit eb50fdef83
7 changed files with 107 additions and 5 deletions

View File

@@ -4573,6 +4573,7 @@ class AgentDB:
card_last4: str | None,
card_brand: str | None,
totp_identifier: str | None = None,
secret_label: str | None = None,
) -> Credential:
async with self.Session() as session:
credential = CredentialModel(
@@ -4586,6 +4587,7 @@ class AgentDB:
totp_identifier=totp_identifier,
card_last4=card_last4,
card_brand=card_brand,
secret_label=secret_label,
)
session.add(credential)
await session.commit()

View File

@@ -901,6 +901,7 @@ class CredentialModel(Base):
totp_identifier = Column(String, nullable=True, default=None)
card_last4 = Column(String, nullable=True)
card_brand = Column(String, nullable=True)
secret_label = Column(String, nullable=True)
created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
modified_at = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow, nullable=False)

View File

@@ -29,6 +29,7 @@ from skyvern.forge.sdk.schemas.credentials import (
CredentialVaultType,
CreditCardCredentialResponse,
PasswordCredentialResponse,
SecretCredentialResponse,
)
from skyvern.forge.sdk.schemas.organizations import (
AzureClientSecretCredentialResponse,
@@ -267,6 +268,14 @@ async def create_credential(
credential_type=data.credential_type,
name=data.name,
)
elif data.credential_type == CredentialType.SECRET:
credential_response = SecretCredentialResponse(secret_label=data.credential.secret_label)
return CredentialResponse(
credential=credential_response,
credential_id=credential.credential_id,
credential_type=data.credential_type,
name=data.name,
)
else:
raise HTTPException(status_code=400, detail=f"Unsupported credential type: {data.credential_type}")
@@ -764,5 +773,13 @@ def _convert_to_response(credential: Credential) -> CredentialResponse:
credential_type=credential.credential_type,
name=credential.name,
)
elif credential.credential_type == CredentialType.SECRET:
credential_response = SecretCredentialResponse(secret_label=credential.secret_label)
return CredentialResponse(
credential=credential_response,
credential_id=credential.credential_id,
credential_type=credential.credential_type,
name=credential.name,
)
else:
raise HTTPException(status_code=400, detail="Credential type not supported")

View File

@@ -15,6 +15,7 @@ class CredentialType(StrEnum):
PASSWORD = "password"
CREDIT_CARD = "credit_card"
SECRET = "secret"
class TotpType(StrEnum):
@@ -49,6 +50,12 @@ class CreditCardCredentialResponse(BaseModel):
brand: str = Field(..., description="Brand of the credit card", examples=["visa"])
class SecretCredentialResponse(BaseModel):
"""Response model for secret credentials."""
secret_label: str | None = Field(default=None, description="Optional label for the stored secret")
class PasswordCredential(BaseModel):
"""Base model for password credentials."""
@@ -115,13 +122,22 @@ class NonEmptyCreditCardCredential(CreditCardCredential):
)
class SecretCredential(BaseModel):
"""Generic secret credential."""
secret_value: str = Field(..., min_length=1, description="The secret value", examples=["sk-abc123"])
secret_label: str | None = Field(default=None, description="Optional label describing the secret")
class CredentialItem(BaseModel):
"""Model representing a credential item in the system."""
item_id: str = Field(..., description="Unique identifier for the credential item", examples=["cred_1234567890"])
name: str = Field(..., description="Name of the credential", examples=["Skyvern Login"])
credential_type: CredentialType = Field(..., description="Type of the credential. Eg password, credit card, etc.")
credential: PasswordCredential | CreditCardCredential = Field(..., description="The actual credential data")
credential: PasswordCredential | CreditCardCredential | SecretCredential = Field(
..., description="The actual credential data"
)
class CreateCredentialRequest(BaseModel):
@@ -129,7 +145,7 @@ class CreateCredentialRequest(BaseModel):
name: str = Field(..., description="Name of the credential", examples=["Amazon Login"])
credential_type: CredentialType = Field(..., description="Type of credential to create")
credential: NonEmptyPasswordCredential | NonEmptyCreditCardCredential = Field(
credential: NonEmptyPasswordCredential | NonEmptyCreditCardCredential | SecretCredential = Field(
...,
description="The credential data to store",
examples=[{"username": "user@example.com", "password": "securepassword123"}],
@@ -140,7 +156,7 @@ class CredentialResponse(BaseModel):
"""Response model for credential operations."""
credential_id: str = Field(..., description="Unique identifier for the credential", examples=["cred_1234567890"])
credential: PasswordCredentialResponse | CreditCardCredentialResponse = Field(
credential: PasswordCredentialResponse | CreditCardCredentialResponse | SecretCredentialResponse = Field(
..., description="The credential data"
)
credential_type: CredentialType = Field(..., description="Type of the credential")
@@ -173,6 +189,7 @@ class Credential(BaseModel):
)
card_last4: str | None = Field(..., description="For credit_card credentials: the last four digits of the card")
card_brand: str | None = Field(..., description="For credit_card credentials: the card brand")
secret_label: str | None = Field(default=None, description="For secret credentials: optional label")
created_at: datetime = Field(..., description="Timestamp when the credential was created")
modified_at: datetime = Field(..., description="Timestamp when the credential was last modified")

View File

@@ -14,6 +14,7 @@ from skyvern.forge.sdk.schemas.credentials import (
CredentialVaultType,
CreditCardCredential,
PasswordCredential,
SecretCredential,
)
from skyvern.forge.sdk.services.credential.credential_vault_service import CredentialVaultService
@@ -36,8 +37,14 @@ class AzureCredentialVaultService(CredentialVaultService):
card_brand: str
card_holder_name: str
class _SecretCredentialDataImage(BaseModel):
type: Literal["secret"]
secret_value: str
secret_label: str | None = None
_CredentialDataImage = Annotated[
Union[_PasswordCredentialDataImage, _CreditCardCredentialDataImage], Field(discriminator="type")
Union[_PasswordCredentialDataImage, _CreditCardCredentialDataImage, _SecretCredentialDataImage],
Field(discriminator="type"),
]
def __init__(self, client: AsyncAzureVaultClient, vault_name: str):
@@ -128,13 +135,20 @@ class AzureCredentialVaultService(CredentialVaultService):
name=db_credential.name,
credential_type=CredentialType.CREDIT_CARD,
)
elif isinstance(data, AzureCredentialVaultService._SecretCredentialDataImage):
return CredentialItem(
item_id=db_credential.item_id,
credential=SecretCredential(secret_value=data.secret_value, secret_label=data.secret_label),
name=db_credential.name,
credential_type=CredentialType.SECRET,
)
else:
raise TypeError(f"Invalid credential type: {type(data)}")
async def _create_azure_secret_item(
self,
organization_id: str,
credential: PasswordCredential | CreditCardCredential,
credential: PasswordCredential | CreditCardCredential | SecretCredential,
) -> str:
if isinstance(credential, PasswordCredential):
data = AzureCredentialVaultService._PasswordCredentialDataImage(
@@ -153,6 +167,12 @@ class AzureCredentialVaultService(CredentialVaultService):
card_brand=credential.card_brand,
card_holder_name=credential.card_holder_name,
)
elif isinstance(credential, SecretCredential):
data = AzureCredentialVaultService._SecretCredentialDataImage(
type="secret",
secret_value=credential.secret_value,
secret_label=credential.secret_label,
)
else:
raise TypeError(f"Invalid credential type: {type(credential)}")

View File

@@ -68,5 +68,19 @@ class CredentialVaultService(ABC):
card_brand=data.credential.card_brand,
totp_identifier=None,
)
elif data.credential_type == CredentialType.SECRET:
return await app.DATABASE.create_credential(
organization_id=organization_id,
name=data.name,
vault_type=vault_type,
item_id=item_id,
credential_type=data.credential_type,
username=None,
totp_type="none",
card_last4=None,
card_brand=None,
totp_identifier=None,
secret_label=data.credential.secret_label,
)
else:
raise Exception(f"Unsupported credential type: {data.credential_type}")