
{"id":71,"date":"2026-09-18T07:33:55","date_gmt":"2026-09-18T07:33:55","guid":{"rendered":"https:\/\/www.0xprathamesh.com\/blog\/?p=71"},"modified":"2026-09-21T06:03:26","modified_gmt":"2026-09-21T06:03:26","slug":"read-only-not-inert-sandbox-channel-containment","status":"publish","type":"post","link":"https:\/\/www.0xprathamesh.com\/blog\/read-only-not-inert-sandbox-channel-containment\/","title":{"rendered":"Read-Only Is Not Inert: How a Sandbox Handed Over the Host&#8217;s Credentials"},"content":{"rendered":"<p class=\"article-deck\"><strong>A CVSS 10.0 was published against vm2 yesterday, the Node.js sandbox that agent frameworks and code interpreters use to run untrusted, model-generated JavaScript.<\/strong> The sandbox&#8217;s own read-only proxy over host modules still forwarded method calls, so sandboxed code could register a listener on the host&#8217;s real HTTPS agent and receive live host request options and the host TLS socket, including the Authorization header, every time an unrelated host request released a pooled connection.<\/p>\n<p><strong>Answer first:<\/strong> this is not a coding mistake in a wrapper. It is the wrong place to express a boundary. The protection was a claim about <em>fields<\/em>, that the sandbox cannot read host properties. The attack used a <em>channel<\/em>. Subscribing to an event is a write, and a proxy that forwards method calls forwards it.<\/p>\n<p>The same day, a 29-page paper gave the general result behind that sentence. Containment computed from an observation is a snapshot, and a snapshot misses the paths that appear when the wiring changes.<\/p>\n<h2>The 10.0, step by step<\/h2>\n<p>Start with what the defence was, because it is a sensible design that many teams would accept in review.<\/p>\n<p>A host module is handed to sandboxed code through a read-only proxy. Property reads are blocked, so sandboxed code cannot inspect host state. Method calls are forwarded to the underlying host object, because a module that cannot be called is not usable, and the entire point of handing it over is to let the code call it.<\/p>\n<p>Inside that design is an assumption worth stating in plain words: <strong>reading is the dangerous operation, so if the sandbox cannot read host state, it cannot obtain host state.<\/strong><\/p>\n<p>The assumption fails because some methods write. In the affected versions, <code>Agent.prototype.on()<\/code> was among the forwarded calls, and <code>on<\/code> registers a listener. Sandboxed code could therefore take the host&#8217;s real HTTPS agent and subscribe to its connection-release event. That call succeeded, because method calls were never filtered, only property reads.<\/p>\n<p>Then nothing happened, and this is the part worth sitting with. The sandbox was not reaching out. It had asked the host to notify it, and the host did, because that is what the host object is for. When an unrelated host request finished and released its pooled connection, the listener received the live host request options and the host TLS socket.<\/p>\n<p>What the sandbox then holds: the host&#8217;s Authorization header, the host&#8217;s private destination host and port, a data listener it can attach to the released socket to read subsequent host response bodies in plaintext, and enough to issue its own authenticated requests with the stolen credentials. No host property was ever read through the proxy, so the proxy never had anything to refuse.<\/p>\n<figure>\n<img decoding=\"async\" src=\"https:\/\/www.0xprathamesh.com\/assets\/blog\/containment-channels\/read-only-proxy.svg\" alt=\"A read-only proxy over a host module beside the attack path: sandboxed code calls a forwarded method, subscribes to the host agent's free event, and receives live host request options and the host TLS socket when an unrelated request releases a pooled connection\" width=\"1600\" height=\"1000\" loading=\"lazy\"><figcaption>What the wrapper protected, and the call it forwarded instead.<\/figcaption><\/figure>\n<p>The fix, for completeness, is in 3.11.7, and it is worth noting what kind of fix it is: not a stricter proxy but a removal of the forwarding that made the callback possible. That is the shape this class of bug always takes, because there is no property check you can add to make an event safe.<\/p>\n<h2>The two siblings, and what they show<\/h2>\n<p>Two other advisories were published the same day, both lower severity, both instructive in the same way.<\/p>\n<p>One exposes the host&#8217;s <code>util<\/code> module to the sandbox as an unfiltered shallow copy. On current Node.js that hands sandboxed code <code>util.getCallSites()<\/code>, a stack-introspection API, which returns the host process&#8217;s full call stack: absolute paths, function names, line numbers, including the embedding application&#8217;s own entrypoint.<\/p>\n<p>The other leaks absolute host filesystem paths through error stack formatting. Attacker-supplied code forces the host-realm source transformer to throw, then reads the error&#8217;s <code>.stack<\/code> property, and the bridge forwards that read to the host-realm formatter.<\/p>\n<p>Here is what makes the pair worth reading together. <strong>Both are bypasses of an earlier redaction introduced for a different advisory<\/strong>, and the reason they are bypasses is the same in each case: the redaction was applied to one channel, and the data left through another. In one case the earlier fix covered the <code>Error.prepareStackTrace<\/code> formatting path while <code>getCallSites()<\/code> provided the same information through a different API. In the other, the sandbox-side redaction of host paths was bypassed because the <code>.stack<\/code> read was forwarded to the host&#8217;s own formatter, so the redaction never ran.<\/p>\n<p>Two fixes, both correct as written, both scoped to the channel that had been demonstrated. The information was not scoped to that channel.<\/p>\n<h2>The general result: containment by observation is a snapshot<\/h2>\n<p>Yesterday&#8217;s paper is titled <em>Reachability, Not Observation: Containing Systems Whose Wiring Changes<\/em>, and its opening premise is the same failure in abstract form. Containment decisions, where to put a firewall, which links to monitor, what a program may reach, are computed from an observed structure. Observation is a snapshot. The question the paper asks is what a snapshot misses when the wiring changes over time.<\/p>\n<p>Three of its results are worth carrying around.<\/p>\n<p><strong>A deny-list catches a fraction.<\/strong> The authors planted eight holes in a real application and tested two ways of describing the boundary. The string deny-list that was in place caught <strong>2 of 8<\/strong>. A declared capability map, checked by static reachability over the application&#8217;s call graph, caught <strong>all 8<\/strong>. The reason is structural rather than a matter of effort: a deny-list describes what has been seen, so it cannot cover a hole introduced after the list was written. A capability map describes what exists, so it can.<\/p>\n<p><strong>The gap can be designed in.<\/strong> On a hypercube whose active dimension rotates, a defender reading a snapshot sees zero crossing edges at 93% of instants, while 8,192 edges must be blocked permanently. Add one always-on ring and the defender sees 2 where 8,194 must be blocked. A time-aware defender holds 585 blocks on average, and <strong>one step of clock lag drops its containment to zero<\/strong>. On the real Internet the same gap is far smaller once growth is removed, which is the paper&#8217;s point: the blind spot is not inherited from the world, it is introduced by the design. Round-robin schedules in optical datacentre fabrics have a gap equal to their period.<\/p>\n<p><strong>And on a coding agent, inventoried from the inside,<\/strong> the measurement is the one I would put in front of anyone who believes a network boundary contains an agent. Idle, its instantaneous state cut is zero, because nothing is flowing at that instant and there is nothing to sever. Six channels carry it across a context reset, and <strong>none of them is the network, so severing the network removes zero<\/strong>. The channel that spawns copies is a branching process with a sharp threshold at an approval rate of 1\/b, below which denial is unnecessary and above which denial is insufficient.<\/p>\n<p>The paper&#8217;s closing instruction is the whole argument in seven words: <strong>contain by the paths that exist, not by the behaviour that was seen.<\/strong><\/p>\n<figure>\n<img decoding=\"async\" src=\"https:\/\/www.0xprathamesh.com\/assets\/blog\/containment-channels\/contain-by-paths.svg\" alt=\"Containment expressed over channels rather than fields or observed behaviour, with the paper's measurements: a string deny-list catching two of eight planted holes against a declared capability map catching all eight, and a coding agent whose instantaneous state cut is zero while six non-network channels cross a context reset\" width=\"1600\" height=\"1000\" loading=\"lazy\"><figcaption>Fields, behaviour, channels. Only the third is a description of what exists.<\/figcaption><\/figure>\n<h2>The model: inventory the channels, not the fields<\/h2>\n<p>Here is what I take from the two sources together, stated as something you can apply to a sandbox this week.<\/p>\n<p>For every object you hand to untrusted code, write down <strong>what can reach it and by which mechanism<\/strong>. Not its fields. Not what it has been observed doing. The mechanisms, and there are four families that matter:<\/p>\n<ul>\n<li><strong>Events it emits.<\/strong> If the object emits anything, subscribing is a write, and the payload arrives from the host. This is the vm2 case, and it is the one people miss because the sandbox appears passive while it waits.<\/li>\n<li><strong>Callbacks it accepts.<\/strong> Anything that takes a function and calls it later is a channel in the same direction, and the same reasoning applies to promises, timers and stream handlers.<\/li>\n<li><strong>Formatters and inspectors it delegates to.<\/strong> Error stack formatting, <code>inspect<\/code>, serialisation hooks and logging paths all hand host data to something that will render it. The two sibling advisories are both this family, which is why both bypassed a redaction that had been placed somewhere else.<\/li>\n<li><strong>Reflection it exposes.<\/strong> Stack introspection, <code>constructor<\/code> walks and any API that returns information about the host realm. A dynamic language&#8217;s reflection surface is effectively unbounded, so this family is the reason a denylist approach cannot be completed rather than merely improved.<\/li>\n<\/ul>\n<p>The test that follows is cheap and I would run it before any deployment that executes generated code. Take the object you are about to hand over, and for each family ask whether the sandbox can reach it. Where the answer is yes, you have a channel, and a channel is a path that exists regardless of whether anyone has used it yet.<\/p>\n<p>There is one more implication worth stating, because it changes what a fix means. The vm2 remedy was not a stricter proxy. It removed the forwarding that made the callback possible, because there is no property check that makes an event safe to subscribe to. When the channel is the mechanism, the fix has to be at the channel.<\/p>\n<h2>Three questions for a sandbox review<\/h2>\n<p>If this pattern shows up in a review you are running, these are the three questions I would ask, in this order, because the first one is free and the third one is the one that changes the design.<\/p>\n<p><strong>What is the boundary a claim about?<\/strong> Ask the author to finish the sentence: &#8220;the sandbox cannot &#8230;&#8221;. If the completion is a noun, a field, a path, a module, the boundary is expressed over things, and things are not where the behaviour lives. If the completion is a verb, the boundary is expressed over actions, and it can be tested. Both vm2 leaks and the two sibling findings are cases where the sentence completed with a noun and the behaviour went around it.<\/p>\n<p><strong>Which direction does data flow?<\/strong> A containment design is usually drawn as a filter on the inbound path, because that is the direction people picture. An event listener reverses it: the host calls the sandbox, and data arrives through a path that no inbound filter inspects. The same reversal appears wherever a sandbox registers its own handler, its own stream, or its own completion callback on something the host owns. Ask what the host will deliver to the sandbox without the sandbox asking twice.<\/p>\n<p><strong>Is the description of the boundary generated or maintained?<\/strong> A maintained list is a statement about history. It records what someone noticed and chose to write down, and it needs a process to stay current. A generated description, such as reachability over a call graph, is a statement about the system as it is, and it updates when the system does. The paper&#8217;s two-of-eight result is what a maintained list looks like when something new is added, and it is not a failure of diligence. It is a property of the method.<\/p>\n<p>None of these requires a tool, and that is deliberate. The mechanism in all three advisories was legible from the source: a proxy that forwarded calls, a shallow copy that was not a copy, a read that was forwarded to the host&#8217;s formatter. What the reviews had in common was a description of the boundary that named the wrong surface. Asking the three questions is how you find that before someone demonstrates it.<\/p>\n<h2>What does not follow<\/h2>\n<p>Careful about scope, because both sources are easy to overread.<\/p>\n<p>The vm2 10.0 is a real, severe defect with a clean fix, and it does not mean every JavaScript sandbox is broken. It means a specific pattern, a proxy that blocks reads while forwarding calls, fails to express what its author intended. Other designs that do not hand live host objects to untrusted code at all are not affected by this, and the ecosystem has been moving toward isolating that kind of boundary, which is worth knowing before anyone reads this as an argument that sandboxes are useless.<\/p>\n<p>The paper&#8217;s numbers are geometric results under stated assumptions, and the authors are explicit that the real-Internet gap is modest once growth is removed. The interesting claim is not that the world behaves like a hypercube. It is that where the gap exists, it exists because of the schedule, and that two ways of describing a boundary differ by a factor of four in what they catch.<\/p>\n<p>And neither source is an argument against running generated code. Agents that execute code they wrote are enormously useful, and the whole reason this matters is that the containment is a real design decision rather than an assumption. A deployment that inventories its channels, pins egress and treats approval rate as the branching parameter it is has made the decision deliberately. One that pointed at a sandbox and assumed the question was answered has not.<\/p>\n<h2>Evidence to look for in your own stack<\/h2>\n<table>\n<thead>\n<tr>\n<th>Question<\/th>\n<th>What to look at<\/th>\n<th>Why it matters<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>What host objects does generated code receive?<\/td>\n<td>The sandbox&#8217;s module allow-list, and for each entry whether it is a deep copy, a proxy, or the live host object<\/td>\n<td>A live host object with forwarded methods is the vm2 shape: a boundary expressed over fields with a channel left open<\/td>\n<\/tr>\n<tr>\n<td>Does anything the sandbox can call emit events?<\/td>\n<td>Each handed-over object&#8217;s event surface, and whether subscription is permitted<\/td>\n<td>Subscribing is a write. An emitting object hands host data to whoever listens, without the listener reading anything<\/td>\n<\/tr>\n<tr>\n<td>Where does error handling cross the boundary?<\/td>\n<td>Stack formatting, inspection and logging paths that render host data inside the sandbox<\/td>\n<td>Both sibling advisories bypassed an earlier redaction because the read was forwarded to a host formatter instead<\/td>\n<\/tr>\n<tr>\n<td>How is the boundary described?<\/td>\n<td>Whether it is a deny-list of known-bad items or a declared map of what exists, checked by reachability<\/td>\n<td>In the paper&#8217;s test the deny-list caught 2 of 8 planted holes and the capability map caught all 8<\/td>\n<\/tr>\n<tr>\n<td>What does the agent do while idle?<\/td>\n<td>The instantaneous cut when nothing is running, against the channels that survive a context reset<\/td>\n<td>Zero flow at an instant is not zero reachability. The paper measures six channels crossing a reset, none of them the network<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>Frequently asked questions<\/h2>\n<h3>What happened in the vm2 CVSS 10.0?<\/h3>\n<p>vm2 versions 3.11.3 through 3.11.6 handed host modules to sandboxed code through a read-only proxy that blocked property reads but forwarded method calls. Because <code>Agent.prototype.on()<\/code> was among the forwarded calls, sandboxed code could subscribe to the host&#8217;s real HTTPS agent connection-release event. When an unrelated host request released a pooled connection, the listener received the live host request options and the host TLS socket, including the Authorization header, and could read later host response bodies and issue authenticated requests with the stolen credentials. It is fixed in 3.11.7.<\/p>\n<h3>Why is a read-only proxy not enough?<\/h3>\n<p>Because read-only describes access to fields, and the attack used a method. Method calls were forwarded so the module would remain usable, and some methods mutate: registering an event listener modifies the host object. The proxy therefore permitted a write, the host then delivered live data to the sandbox through the channel the sandbox had opened, and no property read ever occurred for the proxy to refuse.<\/p>\n<h3>How were the two lower-severity vm2 findings related?<\/h3>\n<p>Both bypassed a redaction introduced for an earlier advisory, and both did so by using a channel the redaction did not cover. One exposed stack introspection through the host <code>util<\/code> module, giving the full host call stack with absolute paths, where the earlier fix had covered only the stack-formatting path. The other leaked host paths through error stack formatting because the sandbox&#8217;s read of the stack was forwarded to the host-realm formatter, so the sandbox-side redaction never ran.<\/p>\n<h3>What does &#8220;contain by the paths that exist, not by the behaviour that was seen&#8221; mean in practice?<\/h3>\n<p>It means describing a boundary in terms of reachability rather than activity. A description of behaviour is a snapshot: it covers what has been observed, and it cannot cover a path added afterwards. In the paper&#8217;s test, a string deny-list caught two of eight planted holes while a declared capability map checked by static reachability caught all eight, for exactly that reason. In a sandbox the equivalent is enumerating the channels each handed-over object exposes rather than the fields it happens to have.<\/p>\n<h3>Does this mean code-execution sandboxes are not worth using?<\/h3>\n<p>No. It means the containment has to be described correctly and tested, rather than assumed. A sandbox that receives deep copies or no host objects at all, that constrains what the code can reach, and whose boundary is verified by reachability, is doing real work. The failure in these advisories is narrower and more useful: a boundary expressed over the wrong surface, with an earlier fix scoped to the one channel that had been demonstrated.<\/p>\n<h3>What is the one thing to check first?<\/h3>\n<p>Whether any object handed to untrusted code can emit an event and whether subscription is permitted. That is the vm2 case, it is the one that produces a 10.0 rather than an information leak, and it is invisible to a review that checks what the sandbox can read, because subscribing does not read anything.<\/p>\n<h2>Why this belongs in the agent-security conversation<\/h2>\n<p>The sandboxes in these advisories are the ones agent frameworks, code interpreters and notebook-style execution features use to run code a model wrote. That makes the containment primitive load-bearing: prompt injection that produces code is only as dangerous as the environment the code runs in, and the environment is exactly what leaked here.<\/p>\n<p>It also completes a set with the rest of this series. The <a href=\"https:\/\/www.0xprathamesh.com\/blog\/agent-disabled-own-sandbox-cve-2026-82533\/\">containment piece from last week<\/a> was about who may lift a restriction. This one is about whether the restriction describes the right surface in the first place. And <a href=\"https:\/\/www.0xprathamesh.com\/blog\/agent-traffic-looks-like-c2-beacon-detection-gap\/\">yesterday&#8217;s note<\/a> was about a control that could not distinguish two behaviours at the telemetry layer. All three are the same question asked at three layers: what can this boundary actually express.<\/p>\n<h2>Primary sources<\/h2>\n<ul>\n<li><a href=\"https:\/\/github.com\/patriksimek\/vm2\/security\/advisories\/GHSA-h85j-hv3c-qfgq\" target=\"_blank\" rel=\"noopener noreferrer\">GHSA-h85j-hv3c-qfgq: vm2 credential exposure via the HTTPS global agent (CVE-2026-92940, CVSS 10.0)<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/patriksimek\/vm2\/security\/advisories\/GHSA-r273-hxvj-fxhp\" target=\"_blank\" rel=\"noopener noreferrer\">GHSA-r273-hxvj-fxhp: vm2 information disclosure via util.getCallSites() (CVE-2026-92933)<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/patriksimek\/vm2\/security\/advisories\/GHSA-x6m4-chr9-cg97\" target=\"_blank\" rel=\"noopener noreferrer\">GHSA-x6m4-chr9-cg97: vm2 information disclosure via error stack formatting (CVE-2026-92936)<\/a><\/li>\n<li><a href=\"https:\/\/nvd.nist.gov\/vuln\/detail\/CVE-2026-92940\" target=\"_blank\" rel=\"noopener noreferrer\">NVD: CVE-2026-92940<\/a><\/li>\n<li><a href=\"https:\/\/arxiv.org\/abs\/2609.19720\" target=\"_blank\" rel=\"noopener noreferrer\">Reachability, Not Observation: Containing Systems Whose Wiring Changes (arXiv 2609.19720, 2026-09-17)<\/a><\/li>\n<li><a href=\"https:\/\/arxiv.org\/abs\/2609.18145\" target=\"_blank\" rel=\"noopener noreferrer\">Companion paper (arXiv 2609.18145)<\/a><\/li>\n<li><a href=\"https:\/\/cwe.mitre.org\/data\/definitions\/668.html\" target=\"_blank\" rel=\"noopener noreferrer\">CWE-668: Exposure of Resource to Wrong Sphere<\/a><\/li>\n<\/ul>\n<p><!--0XPRATHAMESH-CTA--><\/p>\n<h2>One next action<\/h2>\n<p>If you run an agent that executes tools, <a href=\"https:\/\/github.com\/prathamesh-ops-sudo\/AgentCrash\" target=\"_blank\" rel=\"noopener noreferrer\">AgentCrash<\/a> is an open-source harness that crash-tests it in a synthetic workplace: synthetic tools, visual evidence and regression tests. It is built for exactly this problem, turning a containment claim into something you can run against the system instead of arguing about it in review.<\/p>\n<p><script type=\"application\/ld+json\">{\"@context\":\"https:\/\/schema.org\",\"@type\":\"FAQPage\",\"mainEntity\":[{\"@type\":\"Question\",\"name\":\"What happened in the vm2 CVSS 10.0?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"vm2 3.11.3 through 3.11.6 handed host modules to sandboxed code through a read-only proxy that blocked property reads but forwarded method calls. Sandboxed code could subscribe to the host HTTPS agent connection-release event, and when an unrelated host request released a pooled connection the listener received live host request options and the host TLS socket, including the Authorization header. Fixed in 3.11.7.\"}},{\"@type\":\"Question\",\"name\":\"Why is a read-only proxy not enough to contain untrusted code?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Read-only describes access to fields, and the attack used a method. Method calls were forwarded so the module stayed usable, and some methods mutate: registering an event listener modifies the host object. The proxy permitted that write, and the host then delivered live data to the sandbox through the channel the sandbox had opened, with no property read for the proxy to refuse.\"}},{\"@type\":\"Question\",\"name\":\"What does contain by the paths that exist rather than the behaviour that was seen mean?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Describe a boundary by reachability rather than activity. Behaviour is a snapshot that cannot cover a path added afterwards. In the cited study a string deny-list caught two of eight planted holes while a declared capability map checked by static reachability over the call graph caught all eight.\"}},{\"@type\":\"Question\",\"name\":\"What should be checked first in a code-execution sandbox?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Whether any object handed to untrusted code can emit an event and whether subscription is permitted. Subscribing is a write, the payload arrives from the host, and a review that only checks what the sandbox can read will not notice it.\"}}]}<\/script><\/p>\n<p><script type=\"application\/ld+json\">{\"@context\":\"https:\/\/schema.org\",\"@type\":\"BreadcrumbList\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.0xprathamesh.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Field Notes\",\"item\":\"https:\/\/www.0xprathamesh.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Read-Only Is Not Inert: How a Sandbox Handed Over the Host's Credentials\",\"item\":\"https:\/\/www.0xprathamesh.com\/blog\/read-only-not-inert-sandbox-channel-containment\/\"}]}<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>A vm2 read-only proxy forwarded method calls, so sandboxed code subscribed to a host event and received live credentials. Containment is a channel problem.<\/p>\n","protected":false},"author":0,"featured_media":72,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[73,107,108,109,106,86,105],"class_list":["post-71","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-notes","tag-agent-security","tag-containment","tag-cve-2026-92940","tag-cwe-668","tag-sandbox","tag-supply-chain","tag-vm2"],"jetpack_featured_media_url":"https:\/\/www.0xprathamesh.com\/blog\/wp-content\/uploads\/2026\/09\/read-only-not-inert-sandbox-channel-containment.png","_links":{"self":[{"href":"https:\/\/www.0xprathamesh.com\/blog\/wp-json\/wp\/v2\/posts\/71","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.0xprathamesh.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.0xprathamesh.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/www.0xprathamesh.com\/blog\/wp-json\/wp\/v2\/comments?post=71"}],"version-history":[{"count":2,"href":"https:\/\/www.0xprathamesh.com\/blog\/wp-json\/wp\/v2\/posts\/71\/revisions"}],"predecessor-version":[{"id":82,"href":"https:\/\/www.0xprathamesh.com\/blog\/wp-json\/wp\/v2\/posts\/71\/revisions\/82"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.0xprathamesh.com\/blog\/wp-json\/wp\/v2\/media\/72"}],"wp:attachment":[{"href":"https:\/\/www.0xprathamesh.com\/blog\/wp-json\/wp\/v2\/media?parent=71"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.0xprathamesh.com\/blog\/wp-json\/wp\/v2\/categories?post=71"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.0xprathamesh.com\/blog\/wp-json\/wp\/v2\/tags?post=71"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}