[{"data":1,"prerenderedAt":695},["ShallowReactive",2],{"navigation":3,"/security/threat-model":145,"/security/threat-model-surround":690},[4,28,49,88,101,127],{"title":5,"path":6,"stem":7,"children":8,"icon":27},"Getting Started","/getting-started","1.getting-started/1.index",[9,11,15,19,23],{"title":10,"path":6,"stem":7},"Introduction",{"title":12,"path":13,"stem":14},"Working with Agents","/getting-started/working-with-agents","1.getting-started/2.working-with-agents",{"title":16,"path":17,"stem":18},"Setup a Service Provider","/getting-started/setup-service-provider","1.getting-started/3.setup-service-provider",{"title":20,"path":21,"stem":22},"Setup an Identity Provider","/getting-started/setup-identity-provider","1.getting-started/4.setup-identity-provider",{"title":24,"path":25,"stem":26},"Developers","/getting-started/developers","1.getting-started/5.developers",false,{"title":29,"icon":27,"path":30,"stem":31,"children":32,"page":27},"Guides","/guides","2.guides",[33,37,41,45],{"title":34,"path":35,"stem":36},"How It Works","/guides/how-it-works","2.guides/1.how-it-works",{"title":38,"path":39,"stem":40},"Capabilities Guide","/guides/capabilities-guide","2.guides/2.capabilities-guide",{"title":42,"path":43,"stem":44},"End-to-End Tutorial","/guides/end-to-end-tutorial","2.guides/3.end-to-end-tutorial",{"title":46,"path":47,"stem":48},"Delegation Guide","/guides/delegation-guide","2.guides/4.delegation-guide",{"title":50,"path":51,"stem":52,"children":53,"icon":27},"Ecosystem","/ecosystem","3.ecosystem/1.index",[54,56,60,64,68,72,76,80,84],{"title":55,"path":51,"stem":52},"Overview",{"title":57,"path":58,"stem":59},"grapes CLI","/ecosystem/grapes","3.ecosystem/2.grapes",{"title":61,"path":62,"stem":63},"shapes CLI","/ecosystem/shapes","3.ecosystem/3.shapes",{"title":65,"path":66,"stem":67},"escapes","/ecosystem/escapes","3.ecosystem/4.escapes",{"title":69,"path":70,"stem":71},"OpenApe Proxy","/ecosystem/proxy","3.ecosystem/5.proxy",{"title":73,"path":74,"stem":75},"OpenApe Browser","/ecosystem/browser","3.ecosystem/6.browser",{"title":77,"path":78,"stem":79},"OpenApe Auth","/ecosystem/auth","3.ecosystem/7.auth",{"title":81,"path":82,"stem":83},"OpenApe Grants","/ecosystem/grants","3.ecosystem/8.grants",{"title":85,"path":86,"stem":87},"nuxt-auth-sp","/ecosystem/nuxt-auth-sp","3.ecosystem/9.nuxt-auth-sp",{"title":89,"icon":27,"path":90,"stem":91,"children":92,"page":27},"Security","/security","4.security",[93,97],{"title":94,"path":95,"stem":96},"Compliance","/security/compliance","4.security/1.compliance",{"title":98,"path":99,"stem":100},"Threat Model","/security/threat-model","4.security/2.threat-model",{"title":102,"path":103,"stem":104,"children":105,"icon":27},"Reference","/reference","5.reference/1.index",[106,107,111,115,119,123],{"title":102,"path":103,"stem":104},{"title":108,"path":109,"stem":110},"IdP Configuration","/reference/idp-configuration","5.reference/2.idp-configuration",{"title":112,"path":113,"stem":114},"SP Configuration","/reference/sp-configuration","5.reference/3.sp-configuration",{"title":116,"path":117,"stem":118},"API Endpoints","/reference/api-endpoints","5.reference/4.api-endpoints",{"title":120,"path":121,"stem":122},"escapes Config","/reference/escapes-config","5.reference/5.escapes-config",{"title":124,"path":125,"stem":126},"Proxy Config","/reference/proxy-config","5.reference/6.proxy-config",{"title":128,"path":129,"stem":130,"children":131,"icon":27},"Operations","/operations","6.operations/1.index",[132,133,137,141],{"title":128,"path":129,"stem":130},{"title":134,"path":135,"stem":136},"Deployment","/operations/deployment","6.operations/2.deployment",{"title":138,"path":139,"stem":140},"Troubleshooting","/operations/troubleshooting","6.operations/3.troubleshooting",{"title":142,"path":143,"stem":144},"Monitoring","/operations/monitoring","6.operations/4.monitoring",{"id":146,"title":98,"body":147,"description":683,"extension":684,"links":685,"meta":686,"navigation":687,"path":99,"seo":688,"stem":100,"__hash__":689},"docs/4.security/2.threat-model.md",{"type":148,"value":149,"toc":663},"minimark",[150,154,159,163,175,179,185,190,196,210,213,217,220,225,252,256,271,274,278,285,292,313,317,324,327,331,334,370,377,385,399,403,407,413,423,426,440,444,455,463,473,477,480,484,495,500,503,529,540,545,556,561,576,580],[151,152,98],"h1",{"id":153},"threat-model",[155,156,158],"h2",{"id":157},"email-as-public-input","Email as Public Input",[160,161,162],"p",{},"The user's email is entered at the SP to trigger DNS discovery. This is inherently visible to the SP — an accepted, unclosable information surface.",[160,164,165,169,170,174],{},[166,167,168],"strong",{},"Why it's OK:"," The email is needed for DNS resolution (",[171,172,173],"code",{},"_ddisa.{domain}","). Passkeys ensure that knowing someone's email is insufficient to impersonate them. This is the same model as email itself — knowing an address doesn't give you access.",[155,176,178],{"id":177},"dns-discovery-trust-model","DNS Discovery Trust Model",[160,180,181,182,184],{},"DDISA uses DNS TXT records (",[171,183,173],{},") to discover which IdP is authoritative for a given email domain. This makes DNS a critical part of the trust chain. Here's how the protocol handles DNS-related threats.",[186,187,189],"h3",{"id":188},"dns-spoofing-redirect-to-malicious-idp","DNS Spoofing → Redirect to Malicious IdP",[160,191,192,195],{},[166,193,194],{},"Threat:"," An attacker controls the DNS response (cache poisoning, rogue resolver, local MITM) and points the user to a malicious IdP.",[160,197,198,201,202,205,206,209],{},[166,199,200],{},"Why passkeys neutralize this:"," WebAuthn passkeys are origin-bound. The authenticator signs challenges only for the origin where the credential was originally registered. If an attacker redirects a user to ",[171,203,204],{},"evil-idp.example"," instead of the legitimate ",[171,207,208],{},"idp.example",", the authenticator simply refuses to produce an assertion — the origins don't match. The attacker gets nothing.",[160,211,212],{},"This means DNS spoofing cannot be used to steal credentials or impersonate existing users.",[186,214,216],{"id":215},"enrollment-the-one-real-risk","Enrollment: The One Real Risk",[160,218,219],{},"During first-time enrollment, no passkey exists yet — there is no origin binding to protect the user. If DNS is spoofed at this exact moment, the user could register a passkey with a malicious IdP.",[160,221,222],{},[166,223,224],{},"Mitigations:",[226,227,228,240,246],"ul",{},[229,230,231,234,235,239],"li",{},[166,232,233],{},"Email verification:"," The IdP sends a verification email before completing enrollment. An attacker would need to control both DNS ",[236,237,238],"em",{},"and"," the user's email.",[229,241,242,245],{},[166,243,244],{},"Visual confirmation:"," The user sees the IdP URL in their browser. Unexpected domains are noticeable.",[229,247,248,251],{},[166,249,250],{},"DNSSEC:"," Domain operators should enable DNSSEC to cryptographically sign DNS responses (see below).",[186,253,255],{"id":254},"transport-security-doh","Transport Security (DoH)",[160,257,258,259,262,263,266,267,270],{},"In edge and browser environments, DNS resolution typically uses DNS-over-HTTPS (DoH) via providers like Cloudflare (",[171,260,261],{},"1.1.1.1","), Google (",[171,264,265],{},"8.8.8.8","), or Quad9 (",[171,268,269],{},"9.9.9.9","). The TLS connection to the DoH provider protects against local network-level MITM attacks on DNS queries.",[160,272,273],{},"In Node.js and Bun server environments, DNS resolution uses the system resolver. Security depends on the deployment infrastructure — data center DNS is typically more trustworthy than coffee shop Wi-Fi, but operators should be aware of this difference.",[186,275,277],{"id":276},"dnssec-recommended-not-required","DNSSEC: Recommended, Not Required",[160,279,280,281,284],{},"DNSSEC cryptographically signs DNS responses, protecting against cache poisoning and response tampering. Domain operators using DDISA ",[166,282,283],{},"should"," enable DNSSEC as a best practice.",[160,286,287,288,291],{},"The protocol does not ",[236,289,290],{},"require"," DNSSEC because:",[293,294,295,301,307],"ol",{},[229,296,297,300],{},[166,298,299],{},"Passkey origin binding"," already covers the critical authentication path (existing users cannot be phished via DNS spoofing)",[229,302,303,306],{},[166,304,305],{},"Enrollment"," is a one-time event with additional mitigations (email verification, visual confirmation)",[229,308,309,312],{},[166,310,311],{},"Forcing DNSSEC"," would exclude domains that haven't enabled it, reducing adoption without proportional security gain",[186,314,316],{"id":315},"fallback-behavior","Fallback Behavior",[160,318,319,320,323],{},"When no ",[171,321,322],{},"_ddisa."," TXT record exists for a domain, the SP falls back to a configured default IdP. This is by design — not every domain will publish DDISA records.",[160,325,326],{},"The SP operator controls the fallback behavior: which default IdP to use, or whether to reject domains without DDISA records entirely. This is a deployment decision, not a protocol-level security boundary.",[155,328,330],{"id":329},"minimal-authn-jwt","Minimal AuthN-JWT",[160,332,333],{},"The identity token contains only:",[226,335,336,342,355],{},[229,337,338,341],{},[171,339,340],{},"sub"," — email (consistent from input to token)",[229,343,344,347,348,351,352],{},[171,345,346],{},"act"," — ",[171,349,350],{},"human"," or ",[171,353,354],{},"agent",[229,356,357,360,361,360,364,360,367],{},[171,358,359],{},"iss",", ",[171,362,363],{},"aud",[171,365,366],{},"exp",[171,368,369],{},"nonce",[160,371,372,373,376],{},"Explicitly ",[166,374,375],{},"not"," in the AuthN-JWT:",[226,378,379,382],{},[229,380,381],{},"Name, display name → IdP-internal",[229,383,384],{},"Owner, approver → AuthZ layer (grants)",[160,386,387,390,391,394,395,398],{},[166,388,389],{},"Rationale:"," AuthN says ",[236,392,393],{},"who",". AuthZ says ",[236,396,397],{},"what may they do",". Mixing them increases token size and leaks information the SP doesn't need.",[155,400,402],{"id":401},"grant-security","Grant Security",[186,404,406],{"id":405},"command-hash-binding","Command Hash Binding",[160,408,409,410,412],{},"For ",[171,411,65],{}," and similar tools, grants are bound to the exact command via SHA-256:",[414,415,420],"pre",{"className":416,"code":418,"language":419},[417],"language-text","cmd_hash = sha256(\"apt-get upgrade\")\n","text",[171,421,418],{"__ignoreMap":422},"",[160,424,425],{},"The target system computes the hash locally and compares. If it doesn't match, execution is aborted. This prevents:",[226,427,428,434],{},[229,429,430,433],{},[166,431,432],{},"Substitution attacks"," — approving \"whoami\" but executing \"rm -rf /\"",[229,435,436,439],{},[166,437,438],{},"Replay with different commands"," — the hash won't match",[186,441,443],{"id":442},"dual-accountability","Dual Accountability",[160,445,446,447,450,451,454],{},"Grants track both the ",[166,448,449],{},"agent's identity"," and the ",[166,452,453],{},"approver's identity"," separately. In many setups, the agent owner and the approver are different people:",[226,456,457,460],{},[229,458,459],{},"Agent owner: the person responsible for the agent",[229,461,462],{},"Approver: the person who approved this specific action",[160,464,465,466,468,469,472],{},"Both are recorded in the AuthZ-JWT (",[171,467,340],{}," + ",[171,470,471],{},"decided_by",").",[186,474,476],{"id":475},"default-deny","Default Deny",[160,478,479],{},"No grant = no access. Agents start with zero permissions and must request each one. There is no \"admin agent\" concept — every privileged action requires explicit human approval (or a standing grant).",[186,481,483],{"id":482},"management-token-separation","Management Token Separation",[160,485,486,487,490,491,494],{},"The IdP Management Token (",[171,488,489],{},"NUXT_OPENAPE_MANAGEMENT_TOKEN",") is an ",[166,492,493],{},"infrastructure credential"," — equivalent to a database root password or a cloud provider's admin key. It belongs exclusively to human administrators and must never be accessible to agents.",[160,496,497],{},[166,498,499],{},"Why this boundary is critical:",[160,501,502],{},"If an agent obtains the Management Token, it can bypass the entire grant system:",[293,504,505,511,517,523],{},[229,506,507,510],{},[166,508,509],{},"Self-registration"," — create new agent identities without human approval",[229,512,513,516],{},[166,514,515],{},"Grant self-approval"," — approve its own privilege requests",[229,518,519,522],{},[166,520,521],{},"Identity forgery"," — register arbitrary agents and issue tokens for them",[229,524,525,528],{},[166,526,527],{},"Audit destruction"," — the grant log becomes meaningless because the approver and requester are the same entity",[160,530,531,532,535,536,539],{},"This is not a theoretical risk — it is the single most important security boundary in an OpenApe deployment. The grant system's value depends entirely on the separation between ",[236,533,534],{},"who administers the IdP"," (humans with the Management Token) and ",[236,537,538],{},"who requests permissions through it"," (agents with Ed25519 keys).",[160,541,542],{},[166,543,544],{},"Correct deployment:",[226,546,547,550,553],{},[229,548,549],{},"Store the Management Token in a secrets manager or environment variable accessible only to the deployment infrastructure",[229,551,552],{},"Never write it into agent configuration files, shell histories, or automation scripts that agents can read",[229,554,555],{},"Use separate credentials for CI/CD pipelines — if a pipeline needs IdP access, give it a scoped API key, not the Management Token",[160,557,558],{},[166,559,560],{},"Anti-patterns:",[226,562,563,566,573],{},[229,564,565],{},"Passing the Management Token as a CLI argument to agent processes",[229,567,568,569,572],{},"Storing it in a shared ",[171,570,571],{},".env"," file on a machine where agents run",[229,574,575],{},"Using the Management Token \"temporarily\" for agent enrollment (use the enrollment URL flow instead)",[155,577,579],{"id":578},"credential-overview","Credential Overview",[581,582,583,602],"table",{},[584,585,586],"thead",{},[587,588,589,593,596,599],"tr",{},[590,591,592],"th",{},"Credential",[590,594,595],{},"Who holds it",[590,597,598],{},"Purpose",[590,600,601],{},"Agent access?",[603,604,605,622,636,650],"tbody",{},[587,606,607,611,614,617],{},[608,609,610],"td",{},"Management Token",[608,612,613],{},"Human admin",[608,615,616],{},"IdP administration",[608,618,619],{},[166,620,621],{},"NEVER",[587,623,624,627,630,633],{},[608,625,626],{},"Ed25519 Private Key",[608,628,629],{},"User who operates the agent",[608,631,632],{},"Agent authentication",[608,634,635],{},"Yes (user-owned, generated during enrollment or provided)",[587,637,638,641,644,647],{},[608,639,640],{},"Passkey",[608,642,643],{},"Human user",[608,645,646],{},"Human authentication",[608,648,649],{},"Hardware-bound",[587,651,652,655,657,660],{},[608,653,654],{},"Session Secret",[608,656,134],{},[608,658,659],{},"Cookie signing",[608,661,662],{},"Infrastructure only",{"title":422,"searchDepth":664,"depth":665,"links":666},3,2,[667,668,675,676,682],{"id":157,"depth":665,"text":158},{"id":177,"depth":665,"text":178,"children":669},[670,671,672,673,674],{"id":188,"depth":664,"text":189},{"id":215,"depth":664,"text":216},{"id":254,"depth":664,"text":255},{"id":276,"depth":664,"text":277},{"id":315,"depth":664,"text":316},{"id":329,"depth":665,"text":330},{"id":401,"depth":665,"text":402,"children":677},[678,679,680,681],{"id":405,"depth":664,"text":406},{"id":442,"depth":664,"text":443},{"id":475,"depth":664,"text":476},{"id":482,"depth":664,"text":483},{"id":578,"depth":665,"text":579},"Security analysis and design decisions.","md",null,{},true,{"title":98,"description":683},"ijNWJ_2imHajkMGJSEEvWru-o2qYqoZDS-mROEPu8Yw",[691,693],{"title":94,"path":95,"stem":96,"description":692,"children":-1},"NIS2, NIST CSF 2.0, and regulatory compliance.",{"title":102,"path":103,"stem":104,"description":694,"children":-1},"Configuration options, API endpoints, and technical specifications.",1774221117377]