The consent screen for AI agents. Whoever asks for consent also writes the words around it, so this screen shows a person exactly what an agent wants, keeps the agent's words from rewriting it, and hands your app the answer.
Use it when an agent connects to your service, or before your own agent spends, sends or deletes.
It is the human-approval step of KYA-OS, the protocol for agent identity and delegation.
npm install @kya-os/consent
The card is the real component. Untick a permission, then press Allow.
Your app received one event
capabilities-allow
That is the whole contract. What it unlocks is up to the trust layer you already have.
- DIDs and credentials
- OAuth
- A policy engine
- Your agent framework
With a KYA-OS runtime, the approval is recorded as a delegation credential.
Decision log
Now let's attack it.
We turned an agent loose on two copies of the same screen, and let it write the words around both. One is
plain HTML. The other is @kya-os/consent.
-
01 Hostile name
A name that forges a badge
The agent is unverified. Its name is markup that adds a green Verified pill and hides the real one. The plain screen builds its headline with
innerHTML, so the markup takes effect. The component shows it as text. -
02 Injected CSS
A rule that hides the expensive part
One stylesheet rule hides the $500 permission while it stays checked. Page CSS reaches every plain element. It can't reach inside a shadow root.
-
03 Page script
A script that clicks Allow
querySelectorAll("button")finds the plain Allow button and clicks it. The component's buttons live in shadow roots, so the script never sees them. -
04 Honesty
Now we cheat
These shadow roots are open, so code that walks into them on purpose can click the real Allow button. Shadow DOM is encapsulation, not a sandbox. If untrusted code runs in your page, show consent on its own page or origin.
@kya-os/consent
AgentWaiting
Scroll to run the first attack.
The wall is a web standard.
Every component renders into a shadow root, a private DOM built into every modern browser. Page CSS can't style what's inside, and page scripts don't find it unless they go looking.
Elements panel, for consent-button
Try it on the real thing: right-click any part of this page, choose Inspect, and expand
#shadow-root (open).
No framework required
Custom elements and shadow DOM ship in the browser. There's no framework runtime to load and no version to keep in step with your app.
-
React is not required
-
Vue is not required
-
Angular is not required
-
Svelte is not required
-
Solid is not required
We ran the snippets at the bottom of this page with React 19 and Vue 3. Angular, Svelte and Solid support custom elements too; we haven't tested them here. Logos belong to their owners.
Read the standards
- W3C, Introduction to Web Components
- WHATWG, Custom elements
- WHATWG, Shadow trees
- MDN, Web Components
Compare with a React or Vue component library
| React or Vue library | Web component | |
|---|---|---|
| The host needs | That framework, at a shared version | A browser |
| It can render | In apps built with that framework | On any page that loads a script |
| Styles | Join the page's cascade | Stay inside a shadow root |
| You talk to it with | Framework props and callbacks | Properties and DOM events |
| What gets reviewed | One build per framework | One build for all |
Where it stops
- It isn't a sandbox. Code in your page can walk open shadow roots, as attack 4 showed.
- It doesn't verify the agent. The identity pill shows what your app passes in.
- It doesn't enforce scopes. Your server checks every scope it receives.
Make it yours.
Bring your brand color or your Tailwind theme, and the sign-in you already have. Styles reach inside
through CSS variables and ::part(), on purpose, and nowhere else. A sign-in step goes in a
slot, and the screen around it stays the same.
Set properties, listen for events. That's the whole contract.
MIT licensed. 18 elements, 84 KB gzipped, one script tag.