Start typing to search packages!
anvil
By @kentiers
Roblox
MirroredAnvil
Server-authoritative Luau foundation for Roblox actions, runtime schemas, resource scopes, and safe remote boundaries.
Anvil gives server code one explicit path for untrusted input:
remote payload
-> schema validation
-> rate limit
-> cooldown
-> authorization
-> domain execution
-> output validation
-> safe client response
Why Anvil
- Trust boundaries are explicit. Raw client values do not reach an action executor before its input schema passes.
- Failure is typed.
Resultand stable error codes model expected gameplay failures without Promise allocation. - Resource ownership is visible.
Scopeowns connections, Instances, callbacks, and cancellable work; destruction is idempotent. - Runtime cost is predictable. Core has no required third-party runtime dependency, polling loop, or hidden remote creation.
Non-goals
Anvil is not an anti-cheat system, DataStore wrapper, UI framework, networking replacement, or game-rule engine. Game code still owns prices, inventory, ownership, damage, permissions, and external-effect compensation.
Install
Anvil is a server-realm Wally package. Pin an exact version:
[server-dependencies]
Anvil = "kentiers/anvil@0.4.0"
wally install
Map Wally's server dependencies into ServerScriptService with Rojo:
{
"name": "MyGame",
"tree": {
"$className": "DataModel",
"ServerScriptService": {
"ServerPackages": { "$path": "ServerPackages" }
}
}
}
Never map Anvil's Action or Transport modules into ReplicatedStorage.
Use case: server-authoritative purchase request
This example accepts only a bounded item identifier. Price, ownership, inventory mutation, and reward remain server decisions inside execute.
--!strict
local ServerScriptService = game:GetService("ServerScriptService")
local AnvilModule = ServerScriptService.ServerPackages.Anvil
local Anvil = require(AnvilModule)
local RobloxRemote = require(AnvilModule.Transport.RobloxRemote)
local purchase = Anvil.Action.new("Purchase", {
input = Anvil.Schema.object({
ItemId = Anvil.Schema.string():minLength(1):maxLength(64),
}),
output = Anvil.Schema.object({ Accepted = Anvil.Schema.boolean() }),
cooldown = require(AnvilModule.Action.Cooldown).new(0.25, os.clock),
rateLimit = require(AnvilModule.Action.RateLimit).new(10, 1, os.clock),
authorize = function()
return Anvil.Result.ok(nil)
end,
execute = function(context)
local input = context.input :: { ItemId: string }
-- Read catalog, price, balance, and ownership from server-owned state.
if input.ItemId == "" then
return Anvil.Result.err("PURCHASE_NOT_ALLOWED")
end
return Anvil.Result.ok({ Accepted = true })
end,
})
local remotes = ServerScriptService:WaitForChild("Remotes")
local purchaseRemote = remotes:WaitForChild("Purchase") :: RemoteEvent
RobloxRemote.new():bindEvent(purchaseRemote, purchase, {})
The caller creates and owns purchaseRemote; Anvil does not create remotes implicitly.
Runtime schemas
Use schemas at every untrusted boundary. Roblox datatypes are explicit, and Instance references require both class and ancestry constraints:
local target = Anvil.Schema.instance({
classNames = { "BasePart" },
ancestor = workspace:WaitForChild("BuildArea"),
})
Available Roblox validators: Schema.vector3(), Schema.cframe(), Schema.color3(), and Schema.enumItem(expectedEnum?).
Passing Schema.instance only proves reference shape and location. It does not prove player ownership, entitlement, placement validity, or permission.
Security and lifecycle
- Input order is validation, rate limit, cooldown, authorization, execution, then output validation.
- Unknown object fields, non-finite numbers, unsupported datatypes, and unconfigured Instances are rejected.
- Client failures expose stable codes, not stack traces or server state.
- Each request transport dispatch owns a
Scopeand destroys it after completion. Scopelifecycle audit is opt-in, server-only, and has no default telemetry or polling.
Read Security model before binding production remotes. Read Architecture for contracts, constraints, and cost model.
Roadmap
| Phase | Focus | Status |
|---|---|---|
| 0.1 | Core: Result, Schema, Scope, Action, transport | Released |
| 0.2 | Reliability: lifecycle helpers, fakes, diagnostics | Released (0.2.0) |
| 0.3 | Optional integration adapters | Released (0.3.0) |
| 0.4 | Transactions and replay | Released (0.4.0) |
Full scope and exit gates: ROADMAP.md.
Verification
wally install
powershell -ExecutionPolicy Bypass -File scripts/test.ps1
powershell -ExecutionPolicy Bypass -File scripts/test-consumer.ps1
The consumer smoke test downloads exact public Wally package version, maps it server-only through Rojo, and exercises valid and invalid Action requests. TestEZ runs through local Roblox Studio; GitHub CI runs format, lint, and strict analysis.
Documentation
- Security model
- Architecture and API contracts
- Tooling and verification
- Optional adapter contracts
- Transactions and replay
- Changelog
- 0.2.0 migration notes
- 0.3.0 migration notes
- 0.4.0 migration notes
- Release notes
Contributing and support
Open a focused issue for bugs, design proposals, or documentation gaps. Report vulnerabilities privately; see SECURITY.md.
Credits
Built for Roblox with Luau, Wally, Rojo, and TestEZ. Anvil is independent software; these projects are not bundled runtime dependencies.
License
MIT © 2026 kentiers.
Package Details
Install command (Click to copy)
Version
0.4.0
License
MIT
Safe for commercial use
Automated license review — not legal advice.
