Probes, not tables
A table of what a product can do is an opinion: somebody disagrees, edits it, and the registry accumulates positions rather than knowledge. So the unit of contribution is a probe — a short reproducible command, what it establishes, and how to read what comes back. A grant claim here points at the probe that produced it, the date, the environment and the output, and a challenge is a rerun with a different result, which is a fact rather than a view.
Grants are public, mandates are private
| Grant | Mandate | |
|---|---|---|
| What it is | What a tool can do | What this organisation expected this agent to do |
| Who can establish it | Anybody with the product, by probe | Only the operator |
| Where it lives | Here: public, contributed, challenged | In your clone, under yours/, gitignored, never uploaded |
| How it is produced | Probes and documentation | An agent asking questions about work, never about capability |
| What it is worth to others | A great deal, because measuring is work | Nothing, and it is nobody's business |
Nothing about your estate leaves your clone. The delta is computed locally from a public grant and a private mandate, and the only things that can flow back are a probe result, a profile correction, or an incident — each a fact about a vendor's product rather than about you. The page that computes the delta runs in your browser for the same reason.
The primitive
capability = verb × object class × reach + reversible?
A specific path, host or mailbox is an instance of a primitive, never a new one, which is the rule that keeps the set finite. Reversibility sits on the primitive, because it decides whether a gap is a nuisance or a loss, and this estate has settled that recoverability decides insurability. These are capability primitives; the counters a policy is written in are meters (measurable primitives), and a document that touches both says which.
- filesystem — files and directories
- process — programs and their execution
- network — endpoints and hosts
- identity — credentials and who the agent can act as
- communication — messages to people
- code — repositories and what lands in them
- money — budgets and spend
- schedule — things that outlive the turn
- browser — what a browser extension or automation can see and do in your browser
| id | family | verb × object × reach | reversible | what it means |
|---|---|---|---|---|
read.file.project | filesystem | read × file × project | yes | Read the project it is working on |
write.file.project | filesystem | write × file × project | with-effort | Change the project it is working on |
read.file.host | filesystem | read × file × host | no | Read any file the account can reach — a record once read is exposure that cannot be unread |
write.file.host | filesystem | write × file × host | with-effort | Change any file the account can reach |
delete.file.host | filesystem | delete × file × host | no | Delete files anywhere the account can reach |
execute.process.host | process | execute × process × host | with-effort | Run programs as the account |
execute.process.self | process | execute × process × self | yes | Run programs inside its own sandbox only |
send.endpoint.allowed | network | send × network-endpoint × tenant | no | Reach a permitted list of hosts — bytes sent are sent |
send.endpoint.world | network | send × network-endpoint × world | no | Reach any host on the internet |
read.credential.host | identity | read × credential × host | no | Read credentials stored where it runs — a credential read is a credential exposed |
authenticate-as.credential.tenant | identity | authenticate-as × credential × tenant | no | Act in accounts with the credentials it holds |
grant.credential.self | identity | grant × credential × self | yes | Change its own permission settings |
send.message.world | communication | send × message × world | no | Send a message to anyone |
read.message.tenant | communication | read × message × tenant | no | Read mail or chat it is connected to |
write.repository.project | code | write × repository × project | with-effort | Commit to the repository it was pointed at |
write.repository.tenant | code | write × repository × tenant | with-effort | Push to a code host (any branch it can reach) |
authenticate-as.credential.signing | code | authenticate-as × credential × tenant | no | Sign commits with the key it holds |
create.record.world | code | create × record × world | no | Publish packages, images or pages under the name it holds |
write.budget.tenant | money | write × budget × tenant | no | Spend money or tokens against an account it holds |
create.schedule.host | schedule | create × schedule × host | yes | Create something that outlives the turn where it runs (a cron, a service) |
read.record.history | filesystem | read × record × host | no | Read a retained record: shell history, past sessions — a transcript read is every earlier session's reach, exposed again |
create.schedule.tenant | schedule | create × schedule × tenant | yes | Create something that outlives the session, on the platform (a routine, a scheduled trigger, a new session) |
read.record.browsing | browser | read × record × host | no | Read every page you visit — what you read, it read |
Source: primitives.json. A proposed primitive that is a specific thing is an instance; one that is a new verb, object class or reach needs a probe.
The probes
Each probe is one heuristic about one distinct behaviour — OpenSSF Scorecard's definition, adopted rather than minted, because this corpus anchors to published vocabulary and diverges only where the subject differs. The result of one probe is a finding in Scorecard's shape (probe, message, outcome, remediation, location) plus the two fields this subject needs: reversible, and tier. Shapes: finding · findings file · profile.
| probe | establishes | command, and how to read it |
|---|---|---|
identity.accountidentity · observed when self-run · safe to run | execute.process.hostread.file.hostwrite.file.hostdelete.file.host | id -u; sudo -n true && echo 'passwordless escalation available' uid 0 or a passwordless sudo means no user boundary stands between the agent and the host: every filesystem row at host reach is established at once, and delete at host reach is irreversible. A refused sudo means the OS user separation is a boundary the grant does not include. packs/grant-and-mandate/tools/measure.py probe_identity — the tier is decided AFTER the escalation test, never in isolation (library entry 2, finding 1) |
filesystem.write-outside-treefilesystem · observed when self-run · safe to run | write.file.host | f=/etc/.probe-$$ ; [ -w /etc ] || f=$HOME/.probe-$$ ; : > "$f" && echo "wrote $f" && rm -f "$f" A zero-byte file created and removed outside the working tree. Success in /etc means writing to system configuration succeeds; success only in $HOME means the account's files are reachable and the system's are not. |
filesystem.credential-presenceidentity · observed when self-run · safe to run | read.credential.hostauthenticate-as.credential.tenantcreate.record.world | for p in .aws/credentials .config/gcloud .azure .git-credentials .config/gh/hosts.yml .ssh .npmrc .pypirc .cargo/credentials .gnupg .sg-send/keys .kube/config; do [ -e "$HOME/$p" ] && echo "present: $p"; done Presence at the usual locations only. A file present is a credential the account holds and a process running as the account can read (read.credential.host, observed by presence); which services it opens is INFERRED from the path, never read. A package-registry token present is publishing under the account's name (create.record.world), inferred the same way. Presence cannot tell WHOSE a credential is: an agent's own signing key and its vault keystore look the same as a person's, so the profile's note says which, on the operator's account. measure.py probe_filesystem, and the published read-only audit tool's module list the assess library cites |
filesystem.history-presencefilesystem · observed when self-run · safe to run | read.record.history | for p in .bash_history .zsh_history .claude/projects .claude/history.jsonl; do [ -e "$HOME/$p" ] && echo "present: $p"; done History retained means the effective grant is the union of everything any prior turn reached, not a tree over the present. Presence only. measure.py probe_history |
process.visibilityprocess · observed when self-run · safe to run | execute.process.hostexecute.process.self | ps -e | wc -l; nproc How many processes the agent can see, and how many CPUs it has. A count near the host's total means process reach is the host; a count of a handful means a sandbox (execute.process.self). |
network.egress-shellnetwork · observed when self-run · safe to run | send.endpoint.worldsend.endpoint.allowed | for h in pypi.org registry.npmjs.org api.github.com example.com sgit.ai pki.sgit.ai; do printf '%s ' $h; curl -s -o /dev/null -w '%{http_code}\n' --max-time 8 https://$h/ || echo fail; done; env | grep -ci '^https_proxy=' Six hosts, statuses recorded. All answering and no proxy: send.endpoint.world. Some refused (a 403 from a CONNECT tunnel, a timeout) or a proxy variable set: send.endpoint.allowed, and the hosts that answered ARE the allow-list as observed. This measures the shell's reach only: a second tool in the same session can reach a different set (the 4 September finding: union seven, intersection one). measure.py probe_egress, widened to six hosts |
network.egress-second-toolnetwork · self-reported when self-run · safe to run | send.endpoint.worldsend.endpoint.allowed | (not runnable from the shell) — the operator lists the hosts the session's fetch/browse tool reached during ordinary use: run.py --fetch-hosts host1,host2 A shell cannot observe another tool. What the second tool reached is the operator's account, so the finding is self-reported, and the hosts it CANNOT reach are unknown from here. The union and intersection with the shell's reach are what make the grant per tool rather than per product. |
vcs.remote-hooks-signingcode · observed when self-run · safe to run | write.repository.tenantwrite.repository.projectread.file.projectwrite.file.projectauthenticate-as.credential.signing | git config --get remote.origin.url; git config --get core.hooksPath; git config --get commit.gpgsign; git config --get gpg.format; git config --get user.signingkey >/dev/null && echo 'signing key configured' A remote means commits can leave the project (write.repository.tenant) — bounded by branch discipline in prose (expectation) unless a hooksPath names a hook that refuses (setting; --no-verify still passes). An attached, writable tree is read.file.project and write.file.project. Signing configured means commits are signed as the account by default: authenticate-as.credential.signing, irreversible. measure.py probe_vcs, plus the signing configuration |
agent.config-presenceidentity · observed when self-run · safe to run | grant.credential.self | for p in $HOME/.claude/settings.json .claude/settings.json $HOME/.claude/settings.local.json; do [ -e "$p" ] && echo "present: $p"; done A readable settings file can be WRITTEN by anything running as the account, which is what makes tool-enforced permissions a setting: the agent can change its own limits. A refused read is itself a boundary observed from inside. Presence only; contents not read. measure.py probe_agent_config |
schedule.persistenceschedule · observed when self-run · safe to run | create.schedule.host | crontab -l 2>&1 | head -1; command -v crontab systemctl at 2>/dev/null; ls /etc/cron.d 2>/dev/null | wc -l Whether anything the agent creates can outlive the turn on this host. No scheduler binary and no cron directory: NotAvailable. A scheduler present and writable: create.schedule.host — but on an EPHEMERAL host (a container destroyed with the session, a CI runner) a cron outlives the turn and dies with the machine, and the profile must say so. A harness's own routines (a scheduled trigger that resumes or spawns a session) are a scheduler at TENANT reach and are recorded by the operator with harness.platform-tools, not by this command. |
identity.key-shaped-envmoney · observed when self-run · safe to run | write.budget.tenantauthenticate-as.credential.tenant | env | cut -d= -f1 | grep -Ei '(_API_KEY|_TOKEN|_SECRET)$' | sort Names only, never values. A key-shaped variable set in the environment is a credential the process holds; one that names a metered API is spend the agent can incur. Which is which is inferred from the name. |
ci.permissions-blockcode · observed when self-run · safe to run | write.repository.tenant | [ -n "$GITHUB_ACTIONS" ] && echo "$GITHUB_WORKFLOW on $GITHUB_REPOSITORY at $GITHUB_REF" && grep -A3 '^permissions:' .github/workflows/*.yml Only in CI. The workflow's permissions block is a grant declared UP FRONT and set above the job by something the job cannot edit: the clearest boundary in the library. contents:read alone means the token cannot write. measure.py probe_ci |
communication.sendcommunication · documented when self-run · described, not run | send.message.worldread.message.tenant | (described, not run) — sending a message is irreversible, so this class is established from the tools configured (a mail or chat connector present) and recorded as documented, never exercised A probe that would send is in the marked class that is described rather than run. The finding's tier is documented at best. |
money.spendmoney · inferred when self-run · described, not run | write.budget.tenant | (described, not run) — a probe that spends is not run; the safe proxy is identity.key-shaped-env Spend is established by the presence of a metered credential (inferred), or by the supplier's meter (observed, out of band), never by spending. |
harness.platform-toolsidentity · self-reported when self-run · safe to run | create.schedule.tenantauthenticate-as.credential.tenantwrite.repository.tenantsend.endpoint.allowedread.file.projectread.record.browsing | (not runnable from the shell) — the operator lists the harness tools the session holds and what each reaches: an API tool that writes to a code host under a platform-scoped token, a tool that creates a scheduled trigger, a fetch tool with its own allow-list A harness tool is a tool: its reach is measured on its own row. Two consequences that a shell probe cannot see. A code-host API tool pushes WITHOUT going through the clone's git hooks, so a hook-based control (a setting) bounds the git tool and not this one; the control on this row is the platform's token scope (a boundary) and nothing else. And a scheduling tool is create.schedule at TENANT reach: what it creates outlives the session, on the platform. Tier: self-reported, by the operator, from the tool list the session was given. |
browser.extension-permissionsbrowser · documented when self-run · safe to run | read.record.browsingsend.endpoint.worldauthenticate-as.credential.tenant | (described, not run) — open the browser's extension page and read the permissions each extension was granted: 'read and change all your data on all websites' is read.record.browsing at web:every-page; host permissions are send.endpoint.world; an extension that acts on logged-in sites is authenticate-as.credential.tenant at id:browser-sessions Documented tier: the permission is what the browser shows, not what the extension does with it. A broad host permission is a grant nobody remembers granting. |
Source: probes.json. Runner: run.py, which runs the safe probes, emits findings/v1, and validates evidence and profiles.
The profiles
The unit of mapping is a tool, not a product: two tools in one session reached different sets of hosts on 4 September, union seven and intersection one, and neither set was the session's. So a profile is a named configuration of a product listing its tools, each with its own grant; the union is what an operator actually carries and the intersection is reported because it is usually nearly empty. A row with no evidence file is visibly a claim rather than a measurement.
| profile | surface | tools | union | ∩ | irreversible | cannot reach | evidence | version |
|---|---|---|---|---|---|---|---|---|
anthropic/claude-code-remote/ccr-containerClaude Code on the web (a remote session container) as a graph → | agentbox | shell (Bash) fetch (WebFetch) harness (MCP and built-in tools) | 15 | 1 | 7 | 4 | measured · file file file | 2026-09-05.2 |
anthropic/claude-code/local-confirmations-offClaude Code (the CLI, on your own machine) as a graph → | cli | shell (Bash) files (Read, Edit, Write) fetch (WebFetch) | 16 | 0 | 8 | 0 | a claim — derived, no probe run | 2026-09-05 |
anthropic/claude-code/local-defaultClaude Code (the CLI, on your own machine) as a graph → | cli | shell (Bash) files (Read, Edit, Write) fetch (WebFetch) | 16 | 0 | 8 | 0 | a claim — derived, no probe run | 2026-09-05 |
anthropic/claude-desktop/defaultClaude Desktop (a desktop app with local tools) as a graph → | desktop | conversation local files and commands (when enabled) | 10 | 1 | 5 | 0 | a claim — derived, no probe run | 2026-09-05 |
anthropic/claude-web/connectors-onClaude (in the browser, with connectors switched on) as a graph → | web | conversation and uploads connectors | 5 | 0 | 3 | 1 | a claim — derived, no probe run | 2026-09-05 |
generic/browser-extension/broad-host-permissionsA browser extension with broad host permissions as a graph → | extension | the extension | 3 | 3 | 3 | 1 | a claim — derived, no probe run | 2026-09-05 |
generic/scheduled-job/service-accountA scheduled job running as a service account as a graph → | service | the job | 7 | 7 | 4 | 1 | a claim — derived, no probe run | 2026-09-05 |
github/actions-runner/ciActions runner (a hosted CI job) as a graph → | ci | the job's shell | 8 | 8 | 3 | 2 | measured · file | 2026-08-26 |
openai/chatgpt-web/defaultChatGPT (in the browser, no connectors) as a graph → | web | conversation and uploads | 1 | 1 | 0 | 2 | a claim — derived, no probe run | 2026-09-05 |
Manifest: profiles/index.json. A profile's version moves when any row moves, and an assessment computed against it goes stale when it does. Labels never say your or as you: what host, tenant and world mean is each profile's to say (reach_names), because for an agent in a vendor's container host is the container and tenant is a scoped token. Each profile also lists what it cannot reach. Every profile, drawn as a graph →
The diff between two profiles
The same assistant on the web, locally, and with confirmations disabled are siblings sharing most rows and differing in a few, and the diff is the answer to “what does turning that on actually give it”.
The evidence
| file | profile | tool | date | independence | findings |
|---|---|---|---|---|---|
anthropic__claude-code-remote__ccr-container__fetch__2026-09-05.json | anthropic/claude-code-remote/ccr-container | fetch | 2026-09-05 | self | 1 True · 0 False · 0 not available/applicable |
anthropic__claude-code-remote__ccr-container__harness__2026-09-05.json | anthropic/claude-code-remote/ccr-container | harness (MCP and built-in tools) | 2026-09-05 | self | 5 True · 0 False · 0 not available/applicable |
anthropic__claude-code-remote__ccr-container__shell__2026-09-05.json | anthropic/claude-code-remote/ccr-container | shell | 2026-09-05 | self | 17 True · 2 False · 5 not available/applicable |
github__actions-runner__ci__shell__2026-08-26.json | github/actions-runner/ci | the job's shell | 2026-08-26 | self | 8 True · 7 False · 2 not available/applicable |
Every file says self: the probes were run by the thing being profiled. The registry's own model says a probe run by an independent party on the same profile is stronger, and the registry shows both when it has them. Independence is the thing worth paying for, and today nobody has.
Incidents
An incident is evidence that demotes a control: a guardrail an incident bypassed was not the guardrail it was claimed to be. A record names the profile, the capability, the control claimed, the rung it was claimed at and the rung it drops to, with a link to the public account. That is how the registry gets more honest over time rather than more confident.
| date | profile | capability | control claimed | rung | |
|---|---|---|---|---|---|
| 2026-08-26 | anthropic/claude-code-remote/ccr-container | write.repository.tenant | a signed mandate compiled into a pre-push hook that git runs in the agent's clone | inline → out-of-band (not a bypass) | the account |
Folder: incidents/. No bypass has been recorded yet; the first would come from the reconciliation job, where a commit that carries no claim is the detection.
The workflow, and where the value arrives
1 fork or clone you now hold every measured grant in the registry 2 name your profiles which of these products and configurations you actually use 3 the grant appears <- value arrives here, before you have written anything 4 point an agent at it it runs the probes it can, and asks the mandate questions 5 the delta is computed locally, in your clone, and rendered 6 contribute back a probe result, a profile correction, an incident. Never your mandate
Step three is the test of the whole thing: anybody who names three products should see a grant they did not know they had, and if they do not, the registry is not yet worth forking. Steps two to five, in the browser →
Run it where the agent lives
git clone https://github.com/SGit-AI/SGit-AI__Website__PKI && cd SGit-AI__Website__PKI python3 probes/run.py --profile <vendor>/<product>/<variant> --tool shell --out probes/evidence/<file>.json python3 probes/run.py validate probes/evidence/<file>.json # the second tool, by report: the hosts you saw it reach python3 probes/run.py --profile ... --tool fetch --fetch-hosts host1,host2 --out ...
The scan cannot run from outside: agent exposure lives inside a laptop, a container, a workspace configuration and a set of connected accounts, and there is nothing to probe from the internet. So the measurement runs where the thing lives and the file is yours until you commit it. Contributions are pull requests — a code host rather than a vault, because a vault publishes read keys and never write keys, and what this needs is exactly what that rule forbids: strangers proposing changes.
What this does not prove
- That any profile is complete. A self-run probe reports what the subject can see; a capability it does not know it has will not appear. A floor, never a census.
- That a derived profile is true of any instance. 7 of the 9 profiles are reasoned from what a surface architecturally is and no probe has been run on them; they are claims, marked as such, until somebody contributes a file.
- Independence. Every evidence file was produced by the environment it describes.
- That the primitive set is right. It is a starting set, will be wrong at the edges from the first week, and the probes are the contribution.
- Anything about you. Nothing here can see your environment; the page that computes your delta runs in your browser.
Specified by brief v0.33.64 (the grant/mandate repository ships probes, not tables), with the vocabulary corrected the same day by the precedents brief. Machine-readable: probes/llms.txt. CC BY 4.0.