grepticon/

Access control

The claims × visibility model — how a token's claims and a file's visibility decide what an agent can read.

Grepticon controls what an agent can read with one small rule, applied to every read. Two sides carry tags:

  • Files carry visibility — a set of tags set when you upload the file. Default: ['*'].
  • Tokens carry claims — a set of tags set when you mint the token. Default: ['*'].

A read can see a file when the token's claims intersect the file's visibility — or when either side is the wildcard '*'. In other words:

visible  ⇔  '*' ∈ claims  or  '*' ∈ visibility  or  (claims ∩ visibility) ≠ ∅

Default: everything is visible

Because both defaults are ['*'], a workspace with no tagging is fully visible to any token you mint from it. Scoping is opt-in. You get isolation between workspaces for free (a token only reaches its own workspace), and you reach for claims and visibility only when you need finer control within a workspace — for example, giving each agent session a view of just its user's files.

A grp_sk_ management key always sees everything in the account, regardless of visibility. The claims × visibility model governs the grp_at_ access tokens you hand to agents. See Authentication.

Invisible means nonexistent

When a token can't see a file, the file doesn't just fail to open — it is indistinguishable from a file that was never uploaded. cat of a non-visible path returns the same not_found as a typo would; ls, find, and grep prune non-visible files before they build their results. Nothing about a hidden file — not its name, not its size, not a fragment of its contents — leaks to a token that lacks the claims for it.

This is what makes claims safe to hand to an agent: a scoped token can't probe for what it isn't allowed to see.

Worked example

Say your handbook workspace holds an HR document only the HR group should read. Tag it on upload:

import { GrepticonClient } from '@grepticon/sdk';

const client = new GrepticonClient({ apiKey: process.env.GREPTICON_API_KEY ?? '' });

// Tag the file so only tokens claiming group:hr can see it.
await client.files.upload(
  'handbook',
  'hr/salary-bands.md',
  '# Compensation bands\n\nBand 5: …\n',
  { visibility: ['group:hr'], contentType: 'text/markdown' },
);

Now mint two tokens with different claims and read the same path with each:

// HR agent: claims intersect the file's visibility → the read succeeds.
const { token: hrToken } = await client.tokens.mint('handbook', {
  claims: ['group:hr'],
});
const hr = new GrepticonClient({ apiKey: hrToken }).session('handbook');
await hr.cat('hr/salary-bands.md'); // returns the file's text

// Everyone else: claims don't intersect → invisible ≡ nonexistent.
const { token: otherToken } = await client.tokens.mint('handbook', {
  claims: ['user:other'],
});
const other = new GrepticonClient({ apiKey: otherToken }).session('handbook');
await other.cat('hr/salary-bands.md'); // not_found — as if it were never uploaded

The group:hr token reads the file; the user:other token gets not_found. A grep for a term inside that document behaves the same way — the HR token matches it, the other token never sees the line.

Claim and visibility tags

Tags are arbitrary strings; you choose a convention that mirrors your authorization model. The common shapes are:

  • user:<id> — a single principal (e.g. user:alice).
  • group:<name> — a role or team (e.g. group:hr, group:eng).
  • '*' — the wildcard: on a file, "anyone can see it"; on a token, "see everything".

A file or token can carry several tags at once — a file visible to ['group:hr', 'group:legal'] is seen by any token claiming either group.

Setting the tags

  • File visibility is set per upload — the visibility option in files.upload, or the ?visibility= query on the REST file routes. Omit it and the file defaults to ['*'].
  • Token claims are set per mint — the claims option in tokens.mint, or the request body on /tokens. Omit it and the token defaults to ['*'].

Because tokens are cheap and short-lived, the idiomatic pattern is to mint one per agent session with exactly the claims that session's user is entitled to — so the agent's reach is a mint-time decision, enforced on every read.

Where to go next

  • Authentication — the credential planes these claims ride on.
  • Tools — how visibility pruning shows up in ls / find / grep.
  • Quickstart — the end-to-end loop this example slots into.

On this page