<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Sean Story - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Sean Story - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/search-labs/author/sean-story</link>
    </image>
    <link>https://www.elastic.co/search-labs/author/sean-story</link>
    <atom:link href="https://www.elastic.co/search-labs/rss/author/sean-story.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Sat, 24 Oct 2026 11:15:02 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Bringing Elastic into Claude: The ‘why,’ the ‘how,’ and the ‘gotchas’]]></title>
    <description><![CDATA[Point your Claude plugin at main and every merge ships as a release, and that’s the least surprising thing that happened between submitting and going live.]]></description>
    <content:encoded><![CDATA[<p>Previously, if you were inside Claude looking for Elastic, you wouldn't find us: </p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7ca6e3c9ef50e62c/6ad8735dfad7344787d08b16/image14.png" alt="Claude Marketplace search for Elastic returning no plugins, before publishing a Claude plugin to the directory" /><p>We wanted to fix that! This blog is the story of publishing a plugin that puts Elastic's capabilities where developers are already working. We already had the <a href="https://github.com/elastic/agent-skills">elastic/agent-skills</a> repository, and we’ve both <a href="https://www.elastic.co/search-labs/blog/agent-skills-elastic">blogged about</a> and <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-skills">documented</a> them, but an official Claude plugin makes it that much easier for our customers to use those skills.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt17cd078ff68f11c1/6ad8737425ebe702b0f0e501/image5.png" alt="Elastic plugin listed in the Claude app’s plugins settings, found by searching the Anthropic Directory for elastic" /><p>Getting here was a journey. Along the way, we learned a lot, and wanted to share our experience. </p><p>It took us three weeks from first commit to live. Claude docs got us 80% of the way, the other 20% was undocumented review criteria and a versioning model that cuts a new release on every commit. There was limited visibility on when, or where, the plugin would appear.</p><p>Here’s what we learned:</p><h2>Agent Skills versus a Claude plugin: Why we needed both</h2><p>As we worked our way through all the reference materials, we realized that <a href="https://github.com/elastic/agent-skills">our Agent Skills</a> were only part of what a Claude plugin can include. Although <a href="https://agentskills.io/home">Agent Skills</a> are a generic open standard, adopted by major agent vendors and ecosystems, a Claude plugin can contain much more; however, some of it may not be as generically applicable across platforms. 
One <a href="https://code.claude.com/docs/en/plugins/manifest-reference#standard-layout">example from Claude Code Docs</a>:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2ab0b01edfd6771f/6ad8739123d2b4245821eb65/image7.png" alt="Claude plugin folder structure showing .claude-plugin/plugin.json plus skills, commands, agents and hooks" /><p>This contains quite a bit more than skills, and we want room to grow into those. </p><p>So we decided that we needed a new repo for our Claude plugin. That way, we’d have a home where all Claude-specific features could live, separate from our generic Agent Skills. Thus was born the <a href="https://github.com/elastic/claude-plugin">elastic/claude-plugin</a> repository.</p><h2>Submitting a plugin to the Claude directory</h2><p>Claude has thorough documentation, and I won’t rehash it all here, but for reference, you may want to read:</p><ul><li><p><a href="https://claude.com/docs/directory/publish">Publish to the directory</a></p></li><li><p><a href="https://claude.com/docs/plugins/pre-submission-checklist">Plugin pre-submission checklist</a></p></li><li><p><a href="https://claude.com/docs/plugins/quickstart">Build your first plugin</a></p></li></ul><p>We went through the above and made sure that we addressed everything on the checklist. But when I tried to submit the plugin at <a href="https://claude.ai/directory/manage">https://claude.ai/directory/manage</a>, I got the following message:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt13aa2c5843238c35/6ad873bffcdfd12cff05ef42/image6.png" alt="Claude error: you don’t have access to organization settings, blocking a Claude plugin directory submission" /><p>I’m an engineer. An individual contributor. I have no business being an “owner” of an account for a software tool used by most Elastic employees. But the IT team that owns it wouldn’t be maintaining and supporting this external-facing plugin. </p><p>This was our first bit of friction, but it also led to our first lesson, namely, <em>if you have a contact at Anthropic, make use of them</em>.
Elastic had already established a partnership with Anthropic, so when we hit this issue, we simply messaged our point of contact, who quickly got back to us:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7edc81534fb0af61/6ad873e3301f93b701ac65c3/image1.png" alt="Anthropic Slack reply explaining a custom role with Directory management permission for plugin submission" /><p>Once we had the right permissions, it was relatively straightforward to fill out the submission form:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte010e9ed8fa7318c/6ad873fbe0c9dc31a2fd1d80/image13.png" alt="Claude plugin submission form fields: repository, plugin path and branch or tag the directory follows" /><p>Despite the simplicity, this is where we made our first two mistakes.</p><h3>Choosing your plugin path: Why <em>not</em> the repository root</h3><p>As the screenshot above shows, this is the folder where Anthropic will look for your <code>.claude-plugin/plugin.json</code>. It’s also the root of what Anthropic’s automated systems will be scanning. </p><p>We started with the default <code>repository root</code> for our plugin path, but this was a mistake, because the files for our own internal development purposes were also at the repository root. They included the <a href="http://claude.md"><code>CLAUDE.md</code></a> that our engineers could use to maintain the plugin, along with our continuous integration (CI) pipelines, tests, and <code>package-lock.json</code>.</p><p>Scanning those files can lead to unexpected holdups during verification:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta34520bdb92501b4/6ad8741cf490a756723b8e4a/image9.png" alt="Claude directory policy hold on package-lock.json, flagging dependencies that install from a lockfile" /><p>We should note that the Anthropic team didn’t let this be a blocker and, after manual review, waived these warnings.</p><h3>Claude plugin versioning: Why we stopped pointing at <code>main</code></h3><p>There’s no single “right” way to version or release software. But at Elastic, we often stick to the following conventions:</p><ul><li><p>Development is done on feature branches (sometimes in forks).</p></li><li><p>Pull requests (PRs) target <code>main</code>.</p></li><li><p>Versioning uses SemVer (<code>MAJOR.MINOR.PATCH</code>).</p></li><li><p>Releases are cut from release branches (<code>MAJOR.MINOR</code>) and tagged (<code>vMAJOR.MINOR.PATCH</code>).</p></li></ul><p>On my initial read of <a href="https://code.claude.com/docs/en/plugins/loading#versions-and-updates">Claude’s plugin versioning documentation</a>, I knew that we’d need a different system for this repo. We only have one live version at once, rather than maintaining multiple majors and minors. So we planned to just use <code>main</code> with a specific <code>version</code> in the plugin metadata. We’d merge PRs into <code>main</code>, as normal, and when ready to release, we’d bump that <code>version</code> and create a Git tag. Easy. </p><p>But that’s not how it works:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7c51f4000c540617/6ad8743a25ebe75079f0e505/image11.png" alt="Claude plugin versions list showing repeated v0.0.1 policy holds from scheduled checks on every commit" /><p>That initial approach actually led to many<code>v0.0.1</code> “versions” of the plugin, one for each merge commit. </p><p>With help from our Anthropic contacts (the astute reader will notice that this is a theme), we got the following clarification:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6dc3b0c4777263ef/6ad8745472826a774fab3e92/image12.png" alt="Anthropic explaining the version field doesn’t pick the commit, recommending a release branch over main" /><p>After realizing this, we changed our plugin to point at a dedicated <code>stable</code> branch. Now we can merge to <code>main</code> as normal but only merge <code>main</code> into <code>stable</code> when we’re ready to release a batch of changes and update our version number. This results in our flow looking like:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt717358e5112cd6b0/6ad874779e743522827455ed/image3.png" alt="Claude plugin branching model: feature branches merge to main, main merges to a stable branch tagged for release" /><h2>What happens after you submit a Claude plugin</h2><p>You wait.</p><p>When we first submitted, we saw only the following:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt70b7ae3f5b220d04/6ad8749884280d3e10538233/image8.png" alt="Claude plugins dashboard showing the Elastic plugin submitted and pending review ten minutes after sending" /><p>While we were in the process of writing this blog, Anthropic made some changes to its UI, providing a bit more visibility. Now the submissions UI looks more like this:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdb70c7fe85936a67/6ad874ad0aea04487e2a5dba/image2.png" alt="Updated Claude plugin dashboard with a Submitted, Security scan, Approved and Published review timeline" /><p>Even so, the review of our first plugin submission took about a week, and that may have been faster than normal as a result of our direct line of contact with Anthropic solution architects and account executives (to stress again how valuable that relationship has been). We were given a rough estimate of nine days for average review time.</p><p>We couldn’t see it then, but we later learned that part of the holdup was that our submission raised a number of warnings to the Anthropic review team:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1f52284c5ec0c647/6ad874c72299f5ae8ab95dfe/image10.png" alt="Claude plugin review log showing policy warnings for credentials, missing icon and download-and-run commands" /><h2>Policy warnings from the automated review</h2><p>In retrospect, the extra visibility is nice; but, at the time, I might have ended up making a bunch of unnecessary changes. The Anthropic team ultimately waived all of these warnings. The takeaway here is to wait for a call to action from the reviewer, rather than jumping straight into trying to resolve every policy warning, even if they sound serious.</p><p><strong>Warning</strong></p><p><strong>What triggered it</strong></p><p><strong>Resolution</strong></p><p>Uses a credential from the user’s machine</p><p>Example <code>curl</code> commands with credentials templated as environment variables.</p><p>Waived after manual review. False positive.</p><p>No icon</p><p>The <code>icon</code> field was undocumented at submission time, and <code>claude plugin validate --strict</code> passed without it.</p><p>Waived. The field is now documented.</p><p>Contains a download-and-run command</p><p>Skill directives instructing an agent to call an API and interpret the response.</p><p>Waived after manual review.</p><p></p><h3>Warning: Uses a credential from the user’s machine</h3><p>The credential warnings pointed at skills that included example <code>curl</code> commands, where the credentials were templated with environment variable syntax. The Elastic CLI was specially designed to ensure that credentials were never exposed to an agentic caller, so we were initially quite alarmed by this report and relieved to discover that these were false positives.</p><h3>Warning: No icon</h3><p>The icon warning surprised us, too. At the time, there was no documentation for an <code>icon</code> field being an option as part of the manifest, and our CI had run <code>claude plugin validate --strict .</code>, so we knew that the manifest was valid without that field.</p><p>Today, the <a href="https://code.claude.com/docs/en/plugins-reference#fields">Plugin manifest: Fields reference</a> <em>does</em> document <code>icon</code>. We take this as a sign that Anthropic is clearly iterating on the plugin experience. New fields show up, but Anthropic doesn’t retroactively block submissions made without them. </p><h3>Warning: Contains a download-and-run command</h3><p>The download-and-run warning also sounded concerning at first, but on closer inspection, it seems to be taking issue with our skills having directives like the following, where API calls are suggested, as is instruction on interpreting the response:</p><p><code>**Verify Kibana connectivity.** Call `GET kbn:/api/status`. If the call fails, stop and surface the error — do not guess endpoints or credentials. Read `version.number` to confirm the cluster meets the 9.4+ requirement.</code></p><h2>Surprise: Your Claude plugin is live</h2><p>When Anthropic concluded its review, the plugin was immediately visible. This didn’t require an extra confirmation step from us, and there was no staging area. We also received no notification that it was live. This truly surprised us. Our plugin was published late on a Thursday, but I didn’t check on it on Friday. Only on the following Monday did we realize that it was live and visible. In our case, no harm done (the skills had been public for months already), but for anyone who’s not shipping something battle-hardened, be sure to do your testing <em>before</em> submitting. </p><h3>Where your Claude plugin actually appears</h3><p>Publishing doesn’t get you into every Claude directory. There are at least four places where a developer might look for plugins:</p><p><strong>Directory</strong></p><p><strong>Visible on publish?</strong></p><p><strong>How to get in</strong></p><p>Claude app’s <strong>Setting</strong> &gt; <strong>Plugins</strong> &gt; <strong>Discover</strong> UI</p><p>Yes</p><p>Submit your plugin at <a href="https://claude.ai/directory/manage">https://claude.ai/directory/manage</a>.</p><p>Claude Marketplace (<a href="https://claude.com/plugins">https://claude.com/plugins</a>, <a href="https://claude.com/marketplace">https://claude.com/marketplace</a>)</p><p>No</p><p>Verification process done separately by Anthropic, after publishing.</p><p><a href="https://github.com/anthropics/claude-plugins-community/">anthropics/claude-plugins-community repo</a></p><p>No</p><p>Curation process done separately by Anthropic, after publishing.</p><p><a href="https://github.com/anthropics/claude-plugins-official/">anthropics/claude-plugins-official repo</a></p><p>No</p><p>Partner plugins can be listed through Anthropic contact.</p><p>When it’s initially published, your plugin only appears in the Anthropic directory in an unverified state, labeled as “Community.” And this is only if your license or account isn’t configured to exclude unverified plugins. This caused some initial confusion internally (our IT department has Claude Enterprise set up to hide “Community” plugins); we could only see our own plugin when we weren’t logged in with our work accounts.</p><p>To get into the other directories and marketplaces, the plugin needs to go through a separate “verification” or “marking” process. Once again, DMs with Anthropic partners proved very helpful:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt86642ee23b56c77b/6ad874f7c149b80e43c3c602/image15.png" alt="Anthropic Slack reply explaining claude.com/marketplace only lists Claude plugins marked verified" /><h2>What’s next for the Elastic Claude plugin</h2><p>We're still working on getting the plugin verified for the web marketplace and on getting it installed for Elastic's own Claude org so our engineers can dogfood it. The plugin is young, and there are a lot of features we’d like to add. </p><p>But if there's one thing I'd want you to take from this post, it's that the hardest parts of this process weren't technical. They were the gaps between what was expected and what actually happened, and every one of those gaps got closed by a conversation with a real person. The tools are getting smarter, but the communities around them still run on people helping people.</p><p>If you're working through publishing your own plugin and get stuck, reach out on our <a href="https://ela.st/slack">Elastic Stack Community Slack</a> or find <a href="https://www.linkedin.com/in/sestory/">me on LinkedIn</a>. And consider sharing what you’ve learned, too.
See you in the marketplace.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/publish-claude-plugin-directory</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/publish-claude-plugin-directory</guid>
    <category><![CDATA[AI Tools ]]></category>
    <category><![CDATA[Developer Experience]]></category>
    <category><![CDATA[Agentic AI]]></category>
    <dc:creator><![CDATA[Sean Story]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt49d535ad85c44096/6ad8732b176afc1af810c437/image4.png" length="0" type="image/png"/>
    <pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Adding document level security (DLS) to your internal knowledge search]]></title>
    <description><![CDATA[Learn how to secure your internal knowledge lake and offer personalized search for your end-users using document level security (DLS).]]></description>
    <content:encoded><![CDATA[<p>There's a good chance that your enterprise is drowning in internal data.</p><p>You've got your issue-tracking, your note-taking, your meeting transcripts, your wiki pages, your video recordings, your chats and IMs and DMs. And don't forget the emails!</p><p>It's no wonder that so many enterprises are trying to create workplace search experiences - giving their employees a centralized, one-stop-shop for searching for internal information.</p><p>With Elastic's catalog of <a href="https://www.elastic.co/guide/en/enterprise-search/current/connectors.html">connectors</a>, this is relatively easy to do. But after you get all your data indexed and ready to be searched, how do you ensure that it is secured? After all, Tess (from Engineering) shouldn't be looking at Bob's (from HR) notes on performance reviews. How can you make sure that each separate user who comes to this unified search bar does gets their own unique view into only the data that they're authorized to view?</p><p>Enter, Document Level Security (DLS).</p><h2>Understanding document level security (DLS) in Elasticsearch</h2><p>Folks who've followed Elasticsearch for a while may already be aware that <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/document-level-security.html">DLS</a> has been an Elasticsearch feature for quite a long time. It's part of the larger theme of <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/authorization.html">user authorization</a>, and is really quite simple. You embed metadata in Elasticsearch documents, and then you craft an Elasticsearch query, filtering based on that document metadata, that describes the user's authorization. That query is used to create an <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.12/defining-roles.html">Elasticsearch Role</a>.</p><p>At query time, when the search user authenticates, their role(s) (if any) is identified, and the embedded query filter (if any) is applied to their searches.</p><p>Let's look at a simplistic example. Say we have two documents:</p>PUT example/_doc/1
{
  "my-data": true,
  "text": "This data is mine"
}

PUT example/_doc/2
{
  "my-data": false,
  "text": "This data belongs to someone else"
}
<p>A query that would fetch only <em>my</em> data would be:</p>GET example/_search
{
  "query": {
    "term": {
      "my-data": {
        "value": true
      }
    }
  }
}
<p>That query can be embedded into a Role, like:</p>POST /_security/role/my_role
{
  "indices": [
    {
      "names": [ "example" ],
      "privileges": ["read"],
      "query": {
        "term": {
          "my-data": {
            "value": true
          }
        }
      }
    }
  ]
}
<p>So if my user is assigned the role <code>my_role</code>, if I just do</p>GET example/_search
<p>I will only see document <code>1</code>, but not document <code>2</code>.</p><p>While this example is simple in theory, it has a relatively large number of moving pieces.</p><ul><li><p>you must ensure that the documents contain the relevant metadata (<code>"my-data": true</code> vs <code>"my-data": false</code>)</p></li><li><p>you must <em>trust</em> that the metadata on those documents is accurate</p></li><li><p>you must create a Role for every search user with a finely crafted Elasticsearch query</p></li><li><p>you must ensure that every role you create correctly maps to the right user at query time</p></li><li><p>you mush ensure that all of the above stays up to date.</p></li></ul><p>That last one is particularly difficult. When people in your enterprise join, leave, switch teams, or get promoted, that requires changes - potentially to both your (meta)data AND your Roles. And if you add in data sources that support sharing or access editing, you're definitely needing to make sure that your (meta)data stays up-to-date.</p><h2>DLS with Elastic connectors</h2><p><a href="https://www.elastic.co/guide/en/enterprise-search/current/dls.html">Connector document level security</a> builds off of the Elasticsearch DLS primitives. For many connectors, this includes syncing the relevant metadata and Role Descriptors to support DLS. This results in the documents in your content index automatically containing metadata (usually in a <code>_allow_access_control</code> field) to describe the people/groups who are authorized to search for this document, as well as documents in a special <code>.search-acl-filter-&lt;index-name&gt;</code> index that contain the Role Descriptor JSON necessary to build a concrete Role or an API key for a given search user.</p><p>You can find <a href="https://www.elastic.co/guide/en/enterprise-search/current/dls.html#dls-availability-prerequisites">which connectors have DLS available here</a>. For this blog, we're going to reference an example application which utilizes the Sharepoint Online connector. This was the first connector we enabled DLS on, but the example could be easily adapted to work with any DLS-enabled connector.</p><p>If your connector is eligible, and you have a Platinum+ Elasticsearch license, you can enable DLS through a toggle on the connector configuration page.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta4d4869b2ef7a27b/6a1711f7964cea84be08bce1/a8d709d6d559891d623d108484326201a7d048d0-1440x666.png" alt="enable-dls" /><p>From there, it's just a matter of running a Full sync and an Access Control sync, and Elasticsearch will have all the data it needs.</p><h2>DLS implementation example</h2><p>And then what?</p><p>Once Elasticsearch has Role Descriptors and document data with sufficient metadata for those role filters, you're ready to build a secure search experience.</p><p>We've built <a href="https://github.com/elastic/elasticsearch-labs/tree/main/example-apps/internal-knowledge-search">an example knowledge search app</a> that we'll use for this blog, and you're welcome to go take a look at its source code. However, we do want to stress that this is an example only - it is not ready to be run in production on its own. Please exercise good judgement and do not run code that you have not read or do not understand.</p><p>This application has a pretty simple architecture.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4ddcfc0dd7551129/6a1711f867045b814245c303/2ee525509acb672b89f36a2b05ad2b75872745d6-864x408.png" alt="dls-simple-architecture" /><p>It is composed of a <a href="https://github.com/elastic/elasticsearch-labs/blob/main/example-apps/internal-knowledge-search/api/app.py">Flask backend</a> and a <a href="https://github.com/elastic/elasticsearch-labs/tree/main/example-apps/internal-knowledge-search/app-ui">React frontend</a>. The backend is configured with environment variables to establish a connection with Elasticsearch.</p>export ELASTICSEARCH_URL=...
export ELASTIC_USERNAME=...
export ELASTIC_PASSWORD=...
<p>Using this connection, the backend provides three endpoints:</p><ol><li><p><code>GET /api/persona</code> This endpoint lists the identifiers for the "identities" or "personas" that the connector found during the Access Control sync. The frontend uses this list to populate a dropdown of personas so to demonstrate how search results change depending on the selected persona.</p></li><li><p><code>GET /api/indices</code> This endpoint lists which indices have been included in your Search Application. The frontend uses this list to allow you to choose which ones to search against.</p></li><li><p><code>GET /api/api_key?persona=&lt;persona&gt;</code> This endpoint creates and returns an Elasticsearch API key based off of a selected persona. In a production system, <code>persona</code> wouldn't be a request argument, but would be inferred from the authentication credentials. This API key is then used by the frontend to issue search requests to Elasticsearch.</p></li></ol><h3>Caveats</h3><p>As stated above, this example should not be used in production. Gaps include:</p><ul><li><p>It does not implement authentication. A production-ready app would need a way for users to authenticate, and have their identities verified, rather than selecting a user from a dropdown.</p></li><li><p>It does not utilze SSL/TLS. The backend currently transmits Elasticsearch API Keys to the frontend over HTTP, not HTTPS.</p></li><li><p>The frontend issues <code>/_search</code> requests directly to Elasticsearch. Depending on the production use case, you may not want to expose Elasticsearch to your end user like this. Instead, it may be advisable to issue requests from the frontend to your backend (again, with authentication implemented), and have the backend translate those requests to Elasticsearch queries.</p></li></ul><h3>Source Code</h3><p>Below we link to the critical pieces of code that are necessary to implement search with DLS.</p><h4>Creating the authenticated user's role descriptor</h4><p><a href="https://github.com/elastic/elasticsearch-labs/blob/ebd2e96de3dc8d56624e70248de4bbac35e2ec71/example-apps/internal-knowledge-search/api/app.py#L121-L166">Code link</a></p>
            identity = elasticsearch_client.get(
                index=identities_index, id=persona)
            permissions = identity["_source"]["query"]["template"]["params"][
                "access_control"
            ]
            role_descriptor = {
                "dls-role": {
                    "cluster": ["all"],
                    "indices": [
                        {
                            "names": [search_app_name],
                            "privileges": ["read"],
                            "query": {
                                "template": {
                                    "params": {"access_control": permissions},
                                    "source": """{
                                        "bool": {
                                            "should": [
                                                {
                                                    "bool": {
                                                        "must_not": {
                                                            "exists": {
                                                                "field": "_allow_access_control"
                                                            }
                                                        }
                                                    }
                                                },
                                                {
                                                    "terms": {
                                                        "_allow_access_control.enum": {{#toJson}}access_control{{/toJson}}
                                                    }
                                                }
                                            ]
                                        }
                                    }""",
                                }
                            },
                        }
                    ],
                    "restriction": {"workflows": ["search_application_query"]},
                }
            }
<p>You may notice that the query template in this role descriptor is significantly more complex than the simple example provided earlier in this blog. This query does several things:</p><ol><li><p>It uses a query template, instead of an explicit query. This makes it easier when reading to separate a long list of permissions from the query syntax.</p></li><li><p>It uses a <code>bool</code> query. This allows us to combine several logical checks.</p></li><li><p>It grants access to any documents that do not contain the <code>_allow_access_control</code> field</p></li><li><p>It grants access to documents where the <code>_allow_access_control</code> field contains a value found in this user's <code>permissions</code></p></li></ol><h4>Creating an API Key from that Role Descriptor</h4><p><a href="https://github.com/elastic/elasticsearch-labs/blob/ebd2e96de3dc8d56624e70248de4bbac35e2ec71/example-apps/internal-knowledge-search/api/app.py#L167-L169">Code link</a></p>
        api_key = elasticsearch_client.security.create_api_key(
            name=search_app_name+"-internal-knowledge-search-example-"+persona, expiration="1h", role_descriptors=role_descriptor)
        return {"api_key": api_key['encoded']}
<h4>Searching with the API Key</h4><p><a href="https://github.com/elastic/elasticsearch-labs/blob/ebd2e96de3dc8d56624e70248de4bbac35e2ec71/example-apps/internal-knowledge-search/app-ui/src/pages/SearchPage.tsx#L76-L107">Code link</a></p>
      const apiKey = searchPersonaAPIKey;

      const client = SearchApplicationClient(
        appName,
        searchEndpoint,
        apiKey,
        {
          facets: {
            description: {
              type: "text",
            },
          },
        },
        {
          disableCache: true,
        }
      );

      const sortArray = Object.values(sorts).map((sort) =&gt; ({
        [sort.title]: sort.sortDirection,
      }));

      const rawResults = await client()
        .query(query)
        .setSort(sortArray)
        .setPageSize(10)
        .addParameter("indices", indexFilter)
        .search();

      const searchResults = rawResults.hits.hits.map((hit: any) =&gt; {
        return mapHitToSearchResult(hit);
      });
]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/dls-internal-knowledge-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/dls-internal-knowledge-search</guid>
    <category><![CDATA[Basics]]></category>
    <dc:creator><![CDATA[Sean Story]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1a0bf009f00b1c80/6a1711fa4a531b3db636aa9f/c7c174d6408b23fca482664c608f9e8849243d96-1440x720.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 22 Jan 2024 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>