forge-auth
Arsenal capability token integration for Forge agents — ANVIL Spec §8.7, §11.3
Arsenal capability token integration for Forge agents — ANVIL Spec §8.7, §11.3
Package contract
| Field | Value |
|---|---|
| Language | rust |
| Source version | 0.2.0 |
| Manifest | forge-rs/crates/forge-auth/Cargo.toml |
| Source files | 7 |
| Evidence | Source reference; registry publication and runtime conformance are separate checks |
Import boundary
use forge_auth;Use a source checkout or your verified private registry. Manifest coordinates identify the package; they do not establish that a public registry release exists.
Crate boundary
The following entries are taken from src/lib.rs. Feature conditions in the exact source still apply.
pub mod auth_context;
pub mod capability;
pub mod delegation;
pub mod error;
pub mod local_capability;
pub mod tool_auth;
pub mod prelude;
pub use crate::auth_context::AuthContext;
pub use crate::capability::{
act_allows_proxy_variable, act_allows_scope, extract_scopes, verify_act,
};
pub use crate::delegation::{delegate_capabilities, DelegationRequest};
pub use crate::error::{ForgeAuthError, ForgeAuthResult};
pub use crate::tool_auth::{
authorize_proxy_tool_invocation, authorize_tool_invocation, ToolAuthorizationDecision,
ToolAuthorizationRequest,
};Source reference
Download package reference JSON. Each original source file and generated declaration artifact has its own SHA-256 digest. Function bodies and constant values are omitted from downloads. These are source declaration inventories, not compiler-resolved rustdoc, TypeDoc, DocC, or Dokka output. Private modules can contain public declarations that are not reachable through the package boundary; consult the entry point before importing.
auth_context.rs
Read declaration text · 10 declaration entries
pub struct AuthContext {
}
pub fn new(identity: ForgeAgentIdentity, act: AgentCapabilityToken) -> Self;
pub fn agent_did(&self) -> &str;
pub fn identity(&self) -> &ForgeAgentIdentity;
pub fn act(&self) -> &AgentCapabilityToken;
pub fn lineage_depth(&self) -> u32;
pub fn sign(&self, message: &[u8]) -> Vec<u8>;
#[instrument(skip(self), fields(agent_did = %self.identity.did(), tool = %tool_name, tier = %tool_tier))]
pub fn authorize_tool(
&self,
tool_name: &str,
tool_tier: ToolTier,
) -> ForgeAuthResult<ToolAuthorizationDecision>;
#[instrument(skip(self, variables), fields(agent_did = %self.identity.did(), tool = %tool_name))]
pub fn authorize_proxy_tool(
&self,
tool_name: &str,
tool_tier: ToolTier,
variables: &[String],
) -> ForgeAuthResult<ToolAuthorizationDecision>;
#[instrument(skip(self, requested_scopes), fields(parent_did = %self.identity.did(), child_name = %name))]
pub fn create_authorized_subagent(
&self,
name: &str,
namespace: &str,
requested_scopes: ScopeSet,
) -> ForgeAuthResult<AuthContext>;capability.rs
Read declaration text · 5 declaration entries
#[instrument(skip(act), fields(token_id = %act.id(), agent_id = %act.subject()))]
pub fn verify_act(act: &AgentCapabilityToken) -> ForgeAuthResult<()>;
pub fn decode_and_check_ttl(act: &AgentCapabilityToken) -> ForgeAuthResult<()>;
pub fn extract_scopes(act: &AgentCapabilityToken) -> Vec<String>;
#[instrument(skip(act), fields(token_id = %act.id(), scope = %scope))]
pub fn act_allows_scope(act: &AgentCapabilityToken, scope: &str) -> ForgeAuthResult<bool>;
#[instrument(skip(act), fields(token_id = %act.id(), variable = %variable))]
pub fn act_allows_proxy_variable(
act: &AgentCapabilityToken,
variable: &str,
) -> ForgeAuthResult<bool>;delegation.rs
Read declaration text · 2 declaration entries
#[derive(Debug)]
pub struct DelegationRequest<'a> {
/// The parent agent's Arsenal ACT.
pub parent_act: &'a AgentCapabilityToken,
/// The parent agent's OAS DID (for error messages).
pub parent_did: String,
/// The child agent's Arsenal identity.
pub child_agent_id: AgentId,
/// The child agent's OAS DID (for error messages).
pub child_did: String,
/// The scopes requested for the child agent.
///
/// Must be a subset of the parent's scopes.
pub requested_scopes: ScopeSet
}
#[instrument(
skip(request),
fields(
parent_did = %request.parent_did,
child_did = %request.child_did,
requested_scope_count = request.requested_scopes.len()
)
)]
pub fn delegate_capabilities(
request: &DelegationRequest<'_>,
) -> ForgeAuthResult<AgentCapabilityToken>;error.rs
Read declaration text · 12 declaration entries
pub type ForgeAuthResult<T> = Result<T, ForgeAuthError>;
#[derive(Debug, thiserror::Error)]
pub enum ForgeAuthError {
/// The Arsenal ACT has expired and is no longer valid.
///
/// Agents must obtain a fresh token before retrying the operation.
///
/// # ANVIL Spec §8.7.1
#[error(
"ACT '{token_id}' for agent '{agent_did}' expired at {expired_at} — obtain a fresh token"
)]
TokenExpired {
/// The unique identifier of the expired token.
token_id: String,
/// The DID of the agent that presented the token.
agent_did: String,
/// The timestamp at which the token expired (ISO 8601).
expired_at: String,
},
/// The agent's ACT does not grant the required scope for the operation.
///
/// The agent must obtain a token with the missing capability, or the
/// operation must be re-scoped to match the agent's granted permissions.
///
/// # ANVIL Spec §8.7.2
#[error(
"agent '{agent_did}' lacks required scope '{required_scope}' — available scopes: {available_scopes:?}"
)]
InsufficientScope {
/// The DID of the agent whose token was checked.
agent_did: String,
/// The scope string that was required but not granted.
required_scope: String,
/// The scopes that the agent's ACT actually grants.
available_scopes: Vec<String>,
},
/// A sub-agent delegation attempted to grant capabilities exceeding the parent's scope.
///
/// Child agent capabilities must be a strict subset of the parent's. This is
/// a security invariant that prevents privilege escalation via delegation.
///
/// # ANVIL Spec §11.3.1
#[error(
"capability escalation denied: child '{child_did}' requested scope '{requested}' but parent '{parent_did}' only grants {available:?}"
)]
CapabilityEscalation {
/// The DID of the parent agent whose ACT was being delegated.
parent_did: String,
/// The DID of the child agent that would receive the delegation.
child_did: String,
/// The scope string that was requested but exceeds the parent's grant.
requested: String,
/// The scopes available in the parent's ACT.
available: Vec<String>,
},
/// Delegation was denied due to constraint violations in the parent's ACT.
///
/// This covers cases where the parent's token does not permit delegation at all,
/// or the target agent is not in the allowed delegates list, or the delegation
/// depth has been exceeded.
///
/// # ANVIL Spec §11.3.2
#[error("delegation from parent '{parent_did}' to child '{child_did}' denied: {reason}")]
DelegationDenied {
/// The DID of the parent agent attempting to delegate.
parent_did: String,
/// The DID of the intended child agent.
child_did: String,
/// Human-readable reason for the denial.
reason: String,
},
/// The ACT is structurally invalid or malformed.
///
/// This indicates the token could not be validated even before checking
/// scopes or time validity. The token may be corrupted, incorrectly
/// constructed, or tampered with.
///
/// # ANVIL Spec §8.7.3
#[error("invalid Arsenal ACT: {reason}")]
InvalidToken {
/// Human-readable explanation of why the token is invalid.
reason: String,
},
/// An error propagated from the underlying Arsenal SDK.
///
/// This wraps errors from `arsenal-core` operations that do not map
/// cleanly to a Forge-specific authorization error. Boxed to keep
/// the overall enum size small.
#[error("arsenal error: {0}")]
Arsenal(Box<ArsenalError>),
/// A proxy request was denied due to insufficient proxy scopes.
#[error(
"proxy access denied for variable '{variable}' — agent '{agent_did}' lacks proxy scope"
)]
ProxyDenied {
/// The DID of the agent.
agent_did: String,
/// The variable name that was denied.
variable: String,
},
/// A proxy request requires consent that has not been granted.
#[error("consent required for agent '{agent_did}' to access variable '{variable}'")]
ConsentRequired {
/// The DID of the agent.
agent_did: String,
/// The variable name requiring consent.
variable: String,
},
/// A fingerprint hash chain mismatch was detected.
#[error("fingerprint mismatch for agent '{agent_did}' — possible key compromise")]
FingerprintMismatch {
/// The DID of the agent with the mismatched fingerprint.
agent_did: String,
},
/// An identity operation failed during an auth workflow.
///
/// This wraps errors from `forge-identity` operations (derivation, lineage)
/// that occur during identity-aware auth flows like sub-agent creation.
///
/// # ANVIL Spec §11.1
#[error("identity error: {reason}")]
IdentityError {
/// Human-readable description of the identity failure.
reason: String,
},
}
#[must_use]
pub fn is_expired(&self) -> bool;
#[must_use]
pub fn is_insufficient_scope(&self) -> bool;
#[must_use]
pub fn is_capability_escalation(&self) -> bool;
#[must_use]
pub fn is_delegation_denied(&self) -> bool;
#[must_use]
pub fn is_invalid_token(&self) -> bool;
#[must_use]
pub fn is_proxy_denied(&self) -> bool;
#[must_use]
pub fn is_consent_required(&self) -> bool;
#[must_use]
pub fn is_fingerprint_mismatch(&self) -> bool;
#[must_use]
pub fn is_identity_error(&self) -> bool;
#[must_use]
pub fn error_code(&self) -> &'static str;lib.rs
Read declaration text · 12 declaration entries
pub mod auth_context;
pub mod capability;
pub mod delegation;
pub mod error;
pub mod local_capability;
pub mod tool_auth;
pub mod prelude;
pub use crate::auth_context::AuthContext;
pub use crate::capability::{
act_allows_proxy_variable, act_allows_scope, extract_scopes, verify_act,
};
pub use crate::delegation::{delegate_capabilities, DelegationRequest};
pub use crate::error::{ForgeAuthError, ForgeAuthResult};
pub use crate::tool_auth::{
authorize_proxy_tool_invocation, authorize_tool_invocation, ToolAuthorizationDecision,
ToolAuthorizationRequest,
};local_capability.rs
Read declaration text · 11 declaration entries
pub const DEV_ISSUER: &str;
pub const DEV_AUDIENCE: &str;
pub const DEV_TOKEN_TTL_SECONDS: i64;
#[derive(Debug, Clone)]
pub struct LocalCapabilityFactory {
}
pub fn new(org_id: &str) -> Self;
pub fn with_ttl(org_id: &str, ttl_seconds: i64) -> Self;
pub fn create_dev_token(&self, agent_did: &str) -> AgentCapabilityToken;
pub fn create_scoped_dev_token(
&self,
agent_did: &str,
scope_strs: &[&str],
) -> ForgeAuthResult<AgentCapabilityToken>;
pub fn issuer(&self) -> &str;
pub fn audience(&self) -> &str;
pub fn default_ttl_seconds(&self) -> i64;tool_auth.rs
Read declaration text · 7 declaration entries
#[derive(Debug)]
pub struct ToolAuthorizationRequest<'a> {
/// The OAS DID of the agent requesting tool invocation.
pub agent_did: String,
/// The name of the tool being invoked.
pub tool_name: String,
/// The tier classification of the tool.
pub tool_tier: ToolTier,
/// The agent's Arsenal ACT, if available.
///
/// When `None`, the request is processed in legacy mode (no ACT checks).
pub act: Option<&'a AgentCapabilityToken>
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ToolAuthorizationDecision {
/// The tool invocation is authorized.
///
/// Returned for:
/// - Tier 1 (Platform) tools — always allowed.
/// - Tier 3 (Embedded) tools — always allowed.
/// - Tier 2 (Host) tools — when the ACT grants the required scope.
Allowed,
/// The tool invocation is denied.
///
/// Returned for Tier 2 (Host) tools when the ACT does not grant the
/// required scope. The `reason` field provides an actionable explanation.
Denied {
/// Human-readable explanation of why the tool was denied.
reason: String,
},
/// No ACT was provided — operating in legacy mode.
///
/// Legacy mode allows tool execution without authorization. This mode
/// exists for backward compatibility during the OAS integration migration.
/// A `WARN`-level log is emitted when this mode is triggered.
///
/// Legacy mode will be removed in a future major version.
LegacyMode,
}
#[must_use]
pub fn is_allowed(&self) -> bool;
#[must_use]
pub fn is_denied(&self) -> bool;
#[must_use]
pub fn is_legacy_mode(&self) -> bool;
#[instrument(
skip(request),
fields(
agent_did = %request.agent_did,
tool = %request.tool_name,
tier = %request.tool_tier,
has_act = request.act.is_some()
)
)]
pub fn authorize_tool_invocation(
request: &ToolAuthorizationRequest<'_>,
) -> ForgeAuthResult<ToolAuthorizationDecision>;
#[instrument(
skip(request, variables),
fields(
agent_did = %request.agent_did,
tool = %request.tool_name,
variable_count = variables.len()
)
)]
pub fn authorize_proxy_tool_invocation(
request: &ToolAuthorizationRequest<'_>,
variables: &[String],
) -> ForgeAuthResult<ToolAuthorizationDecision>;Continue
forge-agent402
Agent-native identity + payment middleware — wraps OpenAgent challenge-response, x402 micropayments, and Arsenal capability grants into agent402::serve() and agent402::connect()
forge-code-safety
Runtime safety primitives for coding agents — worktree isolation, write scoping, delete prevention, approval gates, and audit logging