{"id":579,"date":"2023-10-09T12:58:51","date_gmt":"2023-10-09T10:58:51","guid":{"rendered":"https:\/\/ackeeblockchain.com\/blog\/?p=579"},"modified":"2024-07-04T13:29:11","modified_gmt":"2024-07-04T11:29:11","slug":"staying-safe-with-safe","status":"publish","type":"post","link":"https:\/\/ackee.xyz\/blog\/staying-safe-with-safe\/","title":{"rendered":"Staying safe with Safe"},"content":{"rendered":"<p><span style=\"font-weight: 400;\">Safe multi-sig wallet is one of the core items in the Ethereum ecosystem. It\u2019s been evolving and it\u2019s not what it was in 2017: <\/span><a href=\"https:\/\/forum.safe.global\/t\/draft-the-motivation-and-future-of-safe-contracts\/2143\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">it develops<\/span><\/a><span style=\"font-weight: 400;\"> into a flexible and modular system from relatively simple signature verification.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Since we audited Safe version 1.4.0, we realized the importance of Safe in the Ethereum ecosystem and decided to research it continuously. We collected several tips and potential risks for Safe users and developers building projects around it. Here is the result of our research with several valuable resources for those who decide to get deeper.<\/span><\/p>\n<h3><b>Fallback Handler msg.sender<\/b><\/h3>\n<p><span style=\"font-weight: 400;\">Using <code class=\"codehl\">msg.sender<\/code> is very common in smart contracts. We use it for access control as a mappings key or encode it with other parameters to connect the data to the specific address. However, some design patterns may work in a less straightforward way. One of them is Safe&#8217;s <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">FallbackHandler<\/code><\/span><span style=\"font-weight: 400;\"> contract.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">To extend the functionality of the Safe, we have to deploy a new <\/span><b>separate<\/b><span style=\"font-weight: 400;\"> contract called Fallback Handler. Why? Because Safe is a battle-tested singletone contract, changing it may be not the best idea. Instead, we deploy our proxy pointing to the Safe contract and our handler, where we can extend the functionality of our Safe any way we want.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">How does it work? At first, we add the <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">FallbackHandler<\/code><br \/>\n<\/span><span style=\"font-weight: 400;\"> contract address into the Safe. Then, we call the Safe address with the function from <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">FallbackHandler<\/code><\/span><span style=\"font-weight: 400;\">. Because the function is not implemented inside the Safe contract, the call falls into <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">fallback<\/code><br \/>\n<\/span><span style=\"font-weight: 400;\">, where the call is forwarded to the <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">FallbackHandler<\/code><br \/>\n<\/span><span style=\"font-weight: 400;\">.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">During this call trace, <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">msg.sender<\/code><br \/>\n<\/span><span style=\"font-weight: 400;\"> inside the <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">FallbackHandler<\/code><br \/>\n<\/span><span style=\"font-weight: 400;\"> function will carry the address of Safe, not the original sender. It\u2019s important to keep it in mind because anyone can call <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">FallbackHandler<\/code><br \/>\n<\/span><span style=\"font-weight: 400;\"> on behalf of Safe. For example, the simple access control condition that allows only Safe to call the contract will be useless. If we want to use the original sender&#8217;s address, we use the function <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">msg.sender()<\/code><br \/>\n<\/span><span style=\"font-weight: 400;\"> instead, which is implemented inside the <\/span><a href=\"https:\/\/github.com\/safe-global\/safe-contracts\/blob\/8ffae95faa815acf86ec8b50021ebe9f96abde10\/contracts\/handler\/HandlerContext.sol#L20C1-L28C6\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">contract HandlerContext<\/span><\/a><span style=\"font-weight: 400;\">.<\/span><\/p>\n<pre><code class=\"language-solidity\">function _msgSender() internal pure returns (address sender) {\n    assembly {\n        sender := shr(96, calldataload(sub(calldatasize(), 20)))\n    }\n}<\/code><\/pre>\n<p><span style=\"font-weight: 400;\">As we can see, the function extracts a specific part of the call data, which carries the original sender&#8217;s address. The process of storing the address inside the call data can be seen in the logic of the fallback function inside the <\/span><a href=\"https:\/\/github.com\/safe-global\/safe-contracts\/blob\/8ffae95faa815acf86ec8b50021ebe9f96abde10\/contracts\/base\/FallbackManager.sol#L85C1-L91C95\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">contract<\/span><\/a> <span style=\"font-weight: 400;\"><code class=\"codehl\">FallbackManager<\/code><\/span><span style=\"font-weight: 400;\">.<\/span><\/p>\n<pre><code class=\"language-solidity\">\/\/ The msg.sender address is shifted to the left by 12 bytes to remove the padding\n\/\/ Then the address without padding is stored right after the calldata\nlet senderPtr := allocate(20)\nmstore(senderPtr, shl(96, caller()))\n\/\/ Add 20 bytes for the address appended add the end\nlet success := call(gas(), handler, 0, calldataPtr, add(calldatasize(), 20), 0, 0)\n<\/code><\/pre>\n<h3><b>Broken guard<\/b><\/h3>\n<p><span style=\"font-weight: 400;\">Guard is a smart contract that implements specific data validation logic. It usually contains two main functions <\/span>(an upcoming Safe version may introduce<a class=\"c-link c-link--underline\" href=\"https:\/\/github.com\/safe-global\/safe-contracts\/blob\/8ffae95faa815acf86ec8b50021ebe9f96abde10\/contracts\/base\/ModuleManager.sol#L94\" target=\"_blank\" rel=\"noopener\" data-stringify-link=\"https:\/\/github.com\/safe-global\/safe-contracts\/blob\/8ffae95faa815acf86ec8b50021ebe9f96abde10\/contracts\/base\/ModuleManager.sol#L94\" data-sk=\"tooltip_parent\">\u00a0guard for module transaction<\/a>):<\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\"><code class=\"codehl\">checkTxBeforeExecution<\/code><\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\"><code class=\"codehl\">checkTxAfterExecution<\/code><\/span><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">These functions work as hooks. Guard can implement any data validation that will be performed before and after the transaction execution. The following code snippet simplifies Safe\u2019s <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">execTransaction<\/code><\/span> <a href=\"https:\/\/github.com\/safe-global\/safe-contracts\/blob\/8ffae95faa815acf86ec8b50021ebe9f96abde10\/contracts\/Safe.sol#L139C5-L219C6\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">function<\/span><\/a><span style=\"font-weight: 400;\">:<\/span><\/p>\n<pre><code class=\"language-solidity\">function execTransaction(transaction data) {\n\t\t\t\/\/ ...\n\t\t\t\/\/ Transaction data pre-processing\n\t\t\taddress guard = getGuard();\n\t\t\tif (guard != address(0)) {\n\t\t\t    Guard(guard).checkTransaction(transaction data);\n\t\t\t}\n\t\t\t\/\/ ...\n\t\t\t\/\/ Transaction execution\n\t\t\tif (guard != address(0)) {\n\t\t\t    Guard(guard).checkAfterExecution(txHash, success);\n\t\t\t}\n}<\/code><\/pre>\n<p><span style=\"font-weight: 400;\">The function <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">setGuard()<\/code><\/span><span style=\"font-weight: 400;\"> has to be called for setting a guard. This function is implemented in the contract <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">GuardManager<\/code><\/span><span style=\"font-weight: 400;\">, which Safe inherits from. The function is protected by the <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">authorized<\/code><\/span><span style=\"font-weight: 400;\"> modifier, which allows only Safe self-call to be made, i.e. Safe executes a transaction that calls Safe itself.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">A problem may appear when no recovery mechanism is considered or implemented before setting a guard. If the guard is set, and its code contains a bug, which results in reverting transactions, <\/span><b>the Safe is bricked<\/b><span style=\"font-weight: 400;\">. All the setting functions are called via the Safe self-call, which cannot be performed if it always reverts due to the broken guard code.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Mitigation for such a scenario can be Module. A module is a separate contract with the privilege of executing a Safe transaction via its function <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">executeTxFromModule()<\/code><\/span><span style=\"font-weight: 400;\">. To set a module, the function <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">enableModule()<\/code><\/span><span style=\"font-weight: 400;\"> from the contract <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">ModuleManager<\/code><\/span><span style=\"font-weight: 400;\"> has to be called. If a module is set <\/span><b>before<\/b><span style=\"font-weight: 400;\"> the broken guard, the module transaction can call <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">setGuard()<\/code><\/span><span style=\"font-weight: 400;\"> with a new working guard address. However, Suppose a module is <\/span><b>not<\/b><span style=\"font-weight: 400;\"> set <\/span><b>before<\/b><span style=\"font-weight: 400;\">, and the guard is broken. In that case, there is no way to add a new module because the function <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">enableModule()<\/code><\/span><span style=\"font-weight: 400;\"> is protected by the modifier <\/span><span style=\"font-weight: 400;\">authorized<\/span><span style=\"font-weight: 400;\">.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">In the new version 1.5.0, the guard call is also performed in the module transaction. The mitigation of broken guards became more tricky and almost impossible.<\/span><\/p>\n<h3><b>Powerful modules<\/b><\/h3>\n<p><span style=\"font-weight: 400;\">Module is a separate contract that can perform a transaction on behalf of Safe. The power of Module transactions lies in the fact that no further signatures from owners are needed. Modules can perform <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">CALL<\/code><\/span><span style=\"font-weight: 400;\"> but also <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">DELEGATECALL<\/code><\/span><span style=\"font-weight: 400;\"> to an arbitrary address via <\/span><a href=\"https:\/\/github.com\/safe-global\/safe-contracts\/blob\/8ffae95faa815acf86ec8b50021ebe9f96abde10\/contracts\/base\/ModuleManager.sol#L82C1-L104C6\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">the function <\/span><\/a><span style=\"font-weight: 400;\"><code class=\"codehl\">ExecTransactionFromModule()<\/code><\/span><span style=\"font-weight: 400;\">.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">If a logic on a called address contains state-changing functions, it will change the state of the Safe contract. In extreme scenarios, a contract with <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">selfdestruct<\/code><\/span><span style=\"font-weight: 400;\"> can be called. In a less extreme but no less dangerous scenario, a contract can be written into the storage slots of Safe. For example, It can overwrite important slots with owner addresses, module addresses, or threshold numbers. This leads to the simple advice: <\/span><b>always correctly audit modules before adding them and ensure the owners of modules are trusted<\/b><span style=\"font-weight: 400;\">.<\/span><\/p>\n<h3><b>Trusted Deployer<\/b><\/h3>\n<p><span style=\"font-weight: 400;\">One scenario is where Safe owners may not notice a module is connected to the Safe. Inside the Safe Setup function, the function <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">setupModules()<\/code><\/span><span style=\"font-weight: 400;\"> is called, which performs a <\/span><a href=\"https:\/\/github.com\/safe-global\/safe-contracts\/blob\/8ffae95faa815acf86ec8b50021ebe9f96abde10\/contracts\/base\/ModuleManager.sol#L33C1-L41C6\"><span style=\"font-weight: 400;\"><code class=\"codehl\">DELEGATECALL<\/code><\/span><\/a><span style=\"font-weight: 400;\">.<\/span><\/p>\n<pre><code class=\"language-solidity\">function setupModules(address to, bytes memory data) internal {\n    require(modules[SENTINEL_MODULES] == address(0), &quot;GS100&quot;);\n    modules[SENTINEL_MODULES] = SENTINEL_MODULES;\n    if (to != address(0)) {\n        require(isContract(to), &quot;GS002&quot;);\n        \/\/ Setup has to complete successfully or transaction fails.\n        require(execute(to, 0, data, Enum.Operation.DelegateCall, type(uint256).max), &quot;GS000&quot;);\n    }\n}<\/code><\/pre>\n<p><span style=\"font-weight: 400;\">When an untrusted third-party deployer of the Safe contract decides to perform any malicious operation, he can easily do it via this <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">DELEGATECALL<\/code><br \/>\n<\/span><span style=\"font-weight: 400;\">. Deployer has an unlimited ability to perform any Safe operation during this initial setup phase because owners do not sign the setup call. Deployer can, for example, change storage slots of the Safe, approve tokens, or set up the module, which itself has unlimited power over the Safe in the future. You can learn more about the problem in this<\/span> <a href=\"https:\/\/blog.openzeppelin.com\/backdooring-gnosis-safe-multisig-wallets\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">OpenZeppelin post<\/span><\/a><span style=\"font-weight: 400;\">.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">To avoid this scenario, Safe owners should double-check the setup of the Safe, linked modules, values in storage slots, and, ideally, check the call trace of the setup process.<\/span><\/p>\n<h3><b>tx.origin == msg.sender<\/b><\/h3>\n<p><span style=\"font-weight: 400;\">This pattern is still used in many NFT projects to protect against bots for minting NFTs. However, the pattern is not compatible with Account Abstraction. Smart accounts will have the ability to create a transaction. It means the <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">tx.origin<\/code><\/span><span style=\"font-weight: 400;\"> will be a contract, not an EOA. 2024 may be a year of Account Abstraction, so we should avoid using this pattern not to slow down the adoption process.<\/span><\/p>\n<h3><b>Extracting signatures from calldata<\/b><\/h3>\n<p><span style=\"font-weight: 400;\">Anyone with signatures can execute a transaction. Signatures of Safe owners for a specific transaction are crafted off-chain and passed into the function as input parameters. Once there are enough signatures to pass the threshold, the Safe transaction will be executed. Who is the one who calls the function? It does not matter.\u00a0<\/span><\/p>\n<p><span style=\"font-weight: 400;\">All the data, including signatures, are readable from a <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">calldata<\/code><\/span><span style=\"font-weight: 400;\"> of transactions in the mempool. Thus there is no limitation if anyone reads the <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">calldata<\/code><\/span><span style=\"font-weight: 400;\"> and decides to execute the transaction. For extracting value (MEV), we manage the order of separate transactions with a goal of profit. As there is no access control (having signatures and transaction data is access control), we can pick the Safe transaction from the mempool and include it in our atomic transaction (<\/span><i><span style=\"font-weight: 400;\">the execution is performed in the smart contract<\/span><\/i><span style=\"font-weight: 400;\">). This way of extracting value is even more potent than the classic one, as the extractor has more control over the state.\u00a0<\/span><\/p>\n<p><span style=\"font-weight: 400;\">How to mitigate this potential danger? Use <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">OnlyOwnerGuard<\/code><\/span><span style=\"font-weight: 400;\">, which allows only owners to call the execution function.<\/span><\/p>\n<h3><b>Personal Safe<\/b><\/h3>\n<p><span style=\"font-weight: 400;\">Use your own 1-of-1 multisig Safe wallet. It has many security benefits, even over cold wallets. You can:<\/span><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">rotate your private keys<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">create your own recovery mechanism<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">add new owners later for extra security<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">update verification mechanism<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">stay future-proof<\/span><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">And most importantly, your address will be <\/span><b>the same.<\/b><\/p>\n<h3><b>2-step threshold increase<\/b><\/h3>\n<p><span style=\"font-weight: 400;\">Safe contract (<\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">OwnerManager<\/code><\/span><span style=\"font-weight: 400;\">, which Safe inherits from) contains the function <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">addOwnerWithThreshold()<\/code><\/span><span style=\"font-weight: 400;\">. The function adds a new owner address and increases the threshold simultaneously. There is nothing wrong with it unless you make a small mistake.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Let\u2019s say you have a personal 1-of-1 Safe.\u00a0<\/span><\/p>\n<p><span style=\"font-weight: 400;\">After some time, you decide to add a second owner (for example, the secondary cold wallet address) and increase the security by upgrading the threshold to 2-of-2. The mentioned function is more effective as it can do both steps simultaneously. But what will happen when you mistakenly put the wrong address and increase the threshold? Safe will be bricked. Forever.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">A straightforward mitigation and more error-proof way is to add the owner by calling the function with the same threshold. Then, increase the threshold by using the newly added owner address. In this flow, you can be sure no mistakes appeared.<\/span><\/p>\n<h3><b>Transaction scanning and simulation<\/b><\/h3>\n<p><span style=\"font-weight: 400;\">We can find the <\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">simulate()<\/code><\/span><span style=\"font-weight: 400;\"> function inside the Safe codebase (<\/span><span style=\"font-weight: 400;\"><code class=\"codehl\">SimulateTxAccessor<\/code><\/span><span style=\"font-weight: 400;\"> contract). The function simulates Safe transaction execution. From the simulated transaction, we can extract call-trace, contract state changes, emitted events, balance changes, used gas, etc. All this information can give us more confidence before the real transaction execution. The functionality is integrated into the Safe UX via<\/span> <a href=\"https:\/\/blog.tenderly.co\/case-studies\/safe\/\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">Tenderly<\/span><\/a><span style=\"font-weight: 400;\">, where we can simulate the transaction in one simple click.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">This feature has been taken even further with the introduction of<\/span> <a href=\"https:\/\/safe.mirror.xyz\/rInLWZwD_sf7enjoFerj6FIzCYmVMGrrV8Nhg4THdwI\" target=\"_blank\" rel=\"noopener\"><span style=\"font-weight: 400;\">DeFirewall<\/span><\/a><span style=\"font-weight: 400;\"> by Redefine. This crypto \u201cfirewall\u201d scans transactions before they are signed and checks for potential risks. As a simple scenario, imagine a hacker controlling the website of your favorite DeFi project. When a wallet pops up with transaction data to sign, all looks as usual.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Unfortunately, data are almost unreadable, and we are used to signing without checking. Here is the time when DeFirewall does the job. It simulates the transaction and by using the pop-up window, it highlights maliciously looking events or state changes. As an example:<\/span><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">balance of assets after the transaction goes to zero<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">address of the recipient is a well-known hacker address<\/span><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">Phishing in the blockchain world caused losses of hundreds of millions of dollars $ and spotting a well-made phishing activity can be challenging even for cyber security professionals.<\/span><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Safe multi-sig wallet is one of the core items in the Ethereum ecosystem. It\u2019s been evolving and it\u2019s not what it was in 2017: it develops into a flexible and modular system from relatively simple signature verification. Since we audited Safe version 1.4.0, we realized the importance of Safe in the Ethereum ecosystem and decided to research it continuously. We collected several&hellip;<\/p>\n","protected":false},"author":18,"featured_media":585,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[61,10,80,63],"tags":[24,88,101,68,102],"class_list":["post-579","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-education","category-ethereum","category-solidity","category-tutorial","tag-ethereum","tag-how-to","tag-safe","tag-solidity","tag-tutorial"],"aioseo_notices":[],"featured_image_src":"https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2023\/10\/Safe-600x400.png","featured_image_src_square":"https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2023\/10\/Safe-600x600.png","author_info":{"display_name":"Lukas Bohm","author_link":"https:\/\/ackee.xyz\/blog\/author\/lukas\/"},"_links":{"self":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/posts\/579","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/users\/18"}],"replies":[{"embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/comments?post=579"}],"version-history":[{"count":0,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/posts\/579\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/media\/585"}],"wp:attachment":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/media?parent=579"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/categories?post=579"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/tags?post=579"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}