
{"id":185650,"date":"2026-06-23T08:42:46","date_gmt":"2026-06-23T08:42:46","guid":{"rendered":"https:\/\/mycryptomania.com\/?p=185650"},"modified":"2026-06-23T08:42:46","modified_gmt":"2026-06-23T08:42:46","slug":"fystacks-policy-engine-static-permissions-meet-programmable-controls","status":"publish","type":"post","link":"https:\/\/mycryptomania.com\/?p=185650","title":{"rendered":"Fystack\u2019s Policy Engine: Static Permissions Meet Programmable Controls"},"content":{"rendered":"<p>Every authorization system starts with a simple question:<\/p>\n<p><em>Can this user perform this\u00a0action?<\/em><\/p>\n<p>Static permissions work well for defining responsibilities inside a workspace. A signer can sign. A proposer can propose. An administrator can manage configuration.<\/p>\n<p>That model starts to break down once a request carries\u00a0risk.<\/p>\n<p>Consider a signer creating a withdrawal. The permission check succeeds, but that alone does not tell us much. Is the destination address new? Is the amount unusually large? Has the workspace already moved significant value today? Is the contract call attempting to grant an unlimited token allowance?<\/p>\n<p>None of those questions are answered by the user\u2019s\u00a0role.<\/p>\n<p>The signer may be authorized to act, yet the request itself may still deserve a different outcome.<\/p>\n<p>That is the problem <em>Fystack\u2019s policy engine<\/em> is designed to\u00a0solve.<\/p>\n<h3>The Limits of\u00a0RBAC<\/h3>\n<p>Role-based access control is good at expressing who can perform an action. It is much less effective at expressing the circumstances under which an action should be\u00a0allowed.<\/p>\n<p>A treasury team could create separate roles for every combination of amount, destination type, contract method, network, and time window, but that quickly becomes difficult to\u00a0manage.<\/p>\n<p>Transaction risk is contextual. It depends on the request being made, not just the user making\u00a0it.<\/p>\n<p>Instead of encoding operational judgment into roles, Fystack evaluates each request against the facts available at\u00a0runtime.<\/p>\n<p><strong>Further reading:<\/strong><\/p>\n<h3>The Control\u00a0Model<\/h3>\n<p>Fystack does not replace RBAC or approvals. It separates responsibilities between three\u00a0layers:<\/p>\n<p><strong>RBAC<\/strong> determines who can initiate an\u00a0action.<strong>Policy<\/strong> evaluates the action\u00a0itself.<strong>Approval<\/strong> remains the human checkpoint when automation should not have the final\u00a0word.<\/p>\n<p>The policy engine sits between permission and execution for value-bearing actions.<\/p>\n<p>A request must first pass RBAC. Fystack then builds an evaluation payload and asks the policy engine for a decision.<\/p>\n<p>An example of a programmable policy in Fystack, inspired by AWS policies.<\/p>\n<p>doc := policy.Document{<br \/>  DefaultEffect: &amp;defaultEffect,<br \/>  Policies: []policy.Policy{<br \/>   {<br \/>    Name: &#8220;withdrawal-approval&#8221;,<br \/>    Rules: []policy.Rule{<br \/>     {<br \/>      ID:        &#8220;block_large&#8221;,<br \/>      Effect:    policy.EffectDeny,<br \/>      Condition: &#8220;ValueUSD &gt; 50000&#8221;,<br \/>     },<br \/>     {<br \/>      ID:        &#8220;auto_approve_small_whitelisted&#8221;,<br \/>      Effect:    EffectAutoApprove,<br \/>      Condition: &#8220;IsWhitelisted &amp;&amp; ValueUSD &lt; 5000&#8221;,<br \/>     },<br \/>     {<br \/>      ID:        &#8220;flag_medium&#8221;,<br \/>      Effect:    EffectFlagReview,<br \/>      Condition: &#8220;ValueUSD &gt;= 5000 &amp;&amp; ValueUSD &lt;= 50000&#8221;,<br \/>     },<br \/>     {<br \/>      ID:        &#8220;allow_whitelisted&#8221;,<br \/>      Effect:    policy.EffectAllow,<br \/>      Condition: &#8220;IsWhitelisted&#8221;,<br \/>     },<br \/>    },<br \/>   },<br \/>  },<br \/> }<\/p>\n<p> engine, err := policy.CompileDocument(doc)<br \/> if err != nil {<br \/>  panic(err)<br \/> }<\/p>\n<p> input := map[string]any{<br \/>  &#8220;ValueUSD&#8221;:      3500.0,<br \/>  &#8220;IsWhitelisted&#8221;: true,<br \/> }<\/p>\n<p> decision := engine.Evaluate(context.Background(), input)<\/p>\n<p> switch decision.Effect {<br \/> case policy.EffectDeny:<br \/>  fmt.Println(&#8220;blocked:&#8221;, decision.Message)<br \/> case EffectAutoApprove:<br \/>  fmt.Println(&#8220;auto-approved \u2014 no manual review needed&#8221;)<br \/> case EffectFlagReview:<br \/>  fmt.Println(&#8220;flagged for manual review&#8221;)<br \/> case policy.EffectAllow:<br \/>  fmt.Println(&#8220;allowed \u2014 requires standard approval&#8221;)<br \/> }<\/p>\n<h3>The outcome is intentionally simple<\/h3>\n<p>DENY<br \/>ALLOW<br \/>NO MATCH<\/p>\n<p>DENY blocks the action immediately. The request never becomes a withdrawal, signing job, or any other operation that can move\u00a0assets.<\/p>\n<p>ALLOW means the workspace has defined an explicit rule that permits the action to bypass the approval queue. The request still had to pass RBAC, and the rule still had to match the runtime facts, but no human review is required.<\/p>\n<p>If no rule matches, the policy engine stays silent and Fystack follows the normal approval flow. This is an important distinction: no match is not an implicit allow. It simply falls back to the workspace\u2019s standard governance process.<\/p>\n<h3>Conflict Resolution<\/h3>\n<p>When multiple rules apply, custody-safe behavior takes priority.<\/p>\n<p>DENY &gt; ALLOW<\/p>\n<p>Within the same effect, declaration order provides deterministic matching so operators can identify exactly which rule produced the decision.<\/p>\n<h3>Writing Policies<\/h3>\n<p>A rule follows the shape most operators already understand:<\/p>\n<p>IF condition matches<br \/>THEN effect is ALLOW or DENY<\/p>\n<h3>Withdrawal Controls<\/h3>\n<p>For withdrawals, conditions typically reference amount, destination, whitelist state, velocity, and\u00a0time:<\/p>\n<h3>Contract-Call Controls<\/h3>\n<p>For contract calls, the policy can evaluate method identity, contract metadata, gas parameters, and decoded calldata:<\/p>\n<p>DENY  resource.method_name == &#8220;approve&#8221;<\/p>\n<p>DENY  resource.contract_address == &#8220;0x&#8230;&#8221;<\/p>\n<p>ALLOW resource.method_name == &#8220;transfer&#8221;<\/p>\n<p>Before a rule is stored, Fystack validates that:<\/p>\n<p>the trigger action\u00a0existsthe effect is\u00a0validthe condition is\u00a0presentevery referenced field belongs to the action\u00a0schema<\/p>\n<p>Invalid policy never reaches the approval\u00a0process.<\/p>\n<h3>Evaluating a\u00a0Request<\/h3>\n<p>The evaluator receives a normalized payload regardless of action\u00a0type.<\/p>\n<p>Every request includes workspace and action information, with additional sections such as withdrawal, wallet, resource, and context populated as\u00a0needed.<\/p>\n<p>Evaluation follows a fixed\u00a0path:<\/p>\n<p>Load policy bundle<br \/>\u2192 Match trigger action<br \/>\u2192 Apply wallet targeting<br \/>\u2192 Evaluate conditions<br \/>\u2192 Resolve conflicts<br \/>\u2192 Return decision<\/p>\n<p>The result is a small decision object that answers one question:<\/p>\n<p><em>Should this action be blocked, approved automatically, or continue through approval?<\/em><\/p>\n<p>The response also contains enough context for explanation. Instead of returning a generic rejection, Fystack can identify the policy and rule responsible for the\u00a0outcome.<\/p>\n<h3>Governing Policy\u00a0Changes<\/h3>\n<p>Programmable controls need governance.<\/p>\n<p>A policy can block transactions, but it can also remove approval requirements. That makes policy changes security-sensitive operations in their own\u00a0right.<\/p>\n<p>For that reason, policy changes follow the same review process as other high-impact actions.<\/p>\n<h3>Revisions<\/h3>\n<p>New policies and updates are proposed as revisions rather than becoming active immediately.<\/p>\n<p>Draft<br \/>\u2192 Revision<br \/>\u2192 Approval<br \/>\u2192 Active Policy<\/p>\n<h3>Diffs<\/h3>\n<p>Each revision records exactly what changed, including policy fields and individual rules.<\/p>\n<p>Reviewers can inspect both the resulting policy and the diff against the previous version before approving it.<\/p>\n<h3>Auditability<\/h3>\n<p>Once approved, the new version becomes active, the policy version advances, and the change is recorded in the audit\u00a0trail.<\/p>\n<p>The result is a system where both policy decisions and policy changes remain visible, reviewable, and accountable.<\/p>\n<h3>Operating the\u00a0System<\/h3>\n<p>The operating model is straightforward:<\/p>\n<p>Use roles to determine who can\u00a0act.Use policy to evaluate the\u00a0request.Use approval when the policy layer does not have an explicit\u00a0answer.<\/p>\n<p>A treasury team might begin with only a handful of\u00a0rules:<\/p>\n<p>DENY  withdrawal.value_usd &gt; 10000 &amp;&amp; withdrawal.is_whitelisted == false<\/p>\n<p>ALLOW withdrawal.value_usd &lt; 1000 &amp;&amp; withdrawal.is_whitelisted == true<\/p>\n<p>DENY  resource.method_name == &#8220;approve&#8221;<\/p>\n<p>The goal is not to encode every possible decision in policy. Start with a few controls that represent obvious risk or obvious safety, then let everything else continue through the normal approval\u00a0process.<\/p>\n<p>Over time, teams can expand policy coverage where automation adds value while keeping human review for the cases that remain ambiguous.<\/p>\n<h3>Why This Matters for\u00a0Custody<\/h3>\n<p>Permissions alone cannot distinguish between a routine transaction and an unusual\u00a0one.<\/p>\n<p>In custody systems, that distinction matters. The user may be authorized, but the request still needs to be evaluated against the context in which it\u00a0occurs.<\/p>\n<p>That is the role of\u00a0policy.<\/p>\n<h3>Conclusion<\/h3>\n<p>A signer may be allowed to create a withdrawal.<\/p>\n<p>Whether that withdrawal should proceed is a different question.<\/p>\n<p>Permissions can establish who is authorized to act, but they cannot evaluate the details of the request itself. By the time money is about to move, factors such as destination, amount, transaction history, contract method, and broader activity patterns may matter more than the role attached to the\u00a0user.<\/p>\n<p><em>Fystack\u2019s policy engine<\/em> exists to evaluate those runtime facts before execution. It gives workspaces a way to express operational judgment explicitly, while preserving approval workflows for cases where automation should not make the final decision.<\/p>\n<p>The result is a system that can reason about transactions, not just users, before money\u00a0moves.<\/p>\n<p>We\u2019ve open-sourced Fystack\u2019s core Programmable Policy Engine. Explore it here: <a href=\"https:\/\/github.com\/fystack\/programmable-policy-engine?ref=ghost.fystack.io\">https:\/\/github.com\/fystack\/programmable-policy-engine<\/a>.<\/p>\n<p>Have questions about your custody setup? Share what you are building <a href=\"https:\/\/app.youform.com\/forms\/qyanutyi?ref=ghost.fystack.io\">via the form<\/a> and explore how Fystack\u2019s MPC wallets, KYT integrations, and consolidation engine fit your architecture.<\/p>\n<p>Not ready yet? Join our Telegram for product updates and architecture discussions: <a href=\"https:\/\/t.me\/+9AtC0z8sS79iZjFl?ref=ghost.fystack.io\">https:\/\/t.me\/+9AtC0z8sS79iZjFl<\/a><\/p>\n<p><em>Originally published at <\/em><a href=\"https:\/\/fystack.io\/blog\/fystacks-policy-engine-static-permissions-meet-programmable-controls-2\"><em>https:\/\/fystack.io<\/em><\/a><em> on June 15,\u00a02026.<\/em><\/p>\n<p><a href=\"https:\/\/medium.com\/coinmonks\/fystacks-policy-engine-static-permissions-meet-programmable-controls-ca710b8285c2\">Fystack\u2019s Policy Engine: Static Permissions Meet Programmable Controls<\/a> was originally published in <a href=\"https:\/\/medium.com\/coinmonks\">Coinmonks<\/a> on Medium, where people are continuing the conversation by highlighting and responding to this story.<\/p>","protected":false},"excerpt":{"rendered":"<p>Every authorization system starts with a simple question: Can this user perform this\u00a0action? Static permissions work well for defining responsibilities inside a workspace. A signer can sign. A proposer can propose. An administrator can manage configuration. That model starts to break down once a request carries\u00a0risk. Consider a signer creating a withdrawal. The permission check [&hellip;]<\/p>\n","protected":false},"author":0,"featured_media":185651,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-185650","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-interesting"],"_links":{"self":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/posts\/185650"}],"collection":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=185650"}],"version-history":[{"count":0,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/posts\/185650\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/media\/185651"}],"wp:attachment":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=185650"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=185650"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=185650"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}