Fine-grained access control for LangSmith runs, traces, prompts, and datasets — based on resource tags. Policies layer on top of RBAC: deny always wins, allow can grant access even without RBAC permissions.
LangSmith enforces access in two sequential layers. RBAC controls workspace membership and coarse role-based permissions. ABAC then evaluates resource tags to provide fine-grained scoping — restricting or expanding what each role can actually see.
runs:read)?ABAC requires Helm chart 0.11.28+ and application version 0.12.1+. Enable it before creating policies — choose per-organization SQL or cluster-wide Helm values.
UPDATE organizations
SET config = config || '{"can_use_abac": true}'
WHERE id = '<organization_id>'
AND NOT is_personal;Compose an ABAC access policy interactively. Load a quick example or build from scratch — the JSON panel updates live and can be exported as a ready-to-run Python script.
✓ Allow policies can grant access even without RBAC permissions
Get role IDs via the LangSmith API — GET /api/v1/workspaces/<workspace-id>/roles
{
"name": "<policy-name>",
"effect": "allow",
"condition_groups": [
{
"permission": "projects:read",
"resource_type": "project",
"conditions": [
{
"attribute_name": "resource_tag_key",
"attribute_key": "<tag-key>",
"operator": "equals",
"attribute_value": "<tag-value>"
}
]
}
]
}| Operator | Behavior | Example |
|---|---|---|
| equals | Exact match (case-sensitive) | Environment = prod |
| not_equals | Exclude exact match | Environment != prod |
| equals_ignore_case | Case-insensitive match | team = BACKEND |
| not_equals_ignore_case | Exclude ignoring case | access ≠ SENSITIVE |
| matches | Glob: * any string, ? one char | App = chatbot-* |
| not_matches | Negated glob | Env NOT LIKE *prod* |
| *_if_exists | Matches or tag key absent | Client = Acme (or untagged) |
deny > allow > RBAC fallback
Multiple groups → OR logic
Conditions in group → AND logic
Ready-to-run Python scripts covering common ABAC patterns. Set LANGSMITH_API_KEY to an org admin key before running. For self-hosted, also set LANGCHAIN_ENDPOINT.
LANGSMITH_API_KEY to an org admin key. For self-hosted deployments, also set LANGCHAIN_ENDPOINT to your LangSmith API base URL (e.g. https://langsmith.yourdomain.com/api).#!/usr/bin/env python3
"""
ABAC-only role: Allow reading runs for Environment=dev or staging.
No RBAC runs permissions — access controlled entirely by ABAC.
"""
import os, requests
from datetime import datetime
API_BASE_URL = os.environ.get("LANGCHAIN_ENDPOINT", "https://api.smith.langchain.com")
API_KEY = os.environ["LANGSMITH_API_KEY"]
headers = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
session = requests.Session()
session.headers.update(headers)
# Create a custom role with minimal permissions (no runs:read RBAC).
# projects:read allows the role to browse the project list in the UI.
role_payload = {
"display_name": f"abac_allow_only_{datetime.now().strftime('%H%M%S')}",
"description": "ABAC-only runs access — no RBAC runs permissions",
"permissions": ["projects:read", "workspaces:read"]
}
role_res = session.post(
f"{API_BASE_URL}/api/v1/orgs/current/roles",
json=role_payload
)
role_res.raise_for_status()
role = role_res.json()
print(f"Created role: {role['display_name']} ({role['id']})")
# Each condition_group is OR-ed — access granted if ANY group matches
policy_payload = {
"name": f"allow_env_dev_staging_{datetime.now().strftime('%H%M%S')}",
"description": "Allow runs access for Environment=dev or staging only",
"effect": "allow",
"condition_groups": [
{
"permission": "runs:read",
"resource_type": "project",
"conditions": [{
"attribute_name": "resource_tag_key",
"attribute_key": "Environment",
"operator": "equals",
"attribute_value": "dev"
}]
},
{
"permission": "runs:read",
"resource_type": "project",
"conditions": [{
"attribute_name": "resource_tag_key",
"attribute_key": "Environment",
"operator": "equals",
"attribute_value": "staging"
}]
}
],
"role_ids": [role["id"]]
}
is_cloud = any(d in API_BASE_URL for d in [
"smith.langchain.com", "smith.langchain.dev", "api.smith.langchain.com"
])
url = (f"{API_BASE_URL}/v1/platform/orgs/current/access-policies"
if is_cloud
else f"{API_BASE_URL}/api/v1/platform/orgs/current/access-policies")
resp = session.post(url, json=policy_payload)
resp.raise_for_status()
print(f"Created policy: {resp.json()}")