Why an AI assistant needs a deliberate access workflow
Working across several projects quickly accumulates GitLab tokens, database passwords and server credentials. An assistant can help run checks and investigate failures, but handing it each secret in a message is awkward: the value enters the conversation and can later appear in a command, log or copied example.
In my workflow, secret values live in macOS Keychain and a separate registry describes projects and environments. The assistant selects a named resource, a local tool obtains the credential and performs the operation, and the task receives a result. This article presents a smaller version of that approach that you can reproduce without adopting someone else's infrastructure.
You need a Mac, Python 3.9 or later, and an AI assistant permitted to execute commands locally. A regular browser chat or a cloud container does not automatically gain access to your Mac's Keychain. The example is designed for a local user session; it does not set up a secret store for remote CI workers.
Three components: Keychain, a registry and a local helper
| Component | Contents | Purpose |
|---|---|---|
| macOS Keychain | The token or password value | Store the credential using the operating system. |
| Access registry | Project, environment, resource, endpoint and Keychain reference | Select a precise destination without storing the secret itself. |
| Local helper | Code for a limited operation | Validate the target, obtain the secret, call the service and filter the result. |
| Skill | Instructions for using the helper | Make the procedure available to the assistant. |
A useful access identifier is project / environment / resource, such as demo-web / dev / gitlab. A hostname or the word “server” does not fully identify the intended credential. One project may have several APIs and databases, and one server may host multiple environments.
A fuller implementation should also verify the Git repository root and origin. The teaching helper in this article requires an exact match for the three identifiers in its registry, but does not identify the current checkout or validate its branch. That is an explicit limit of this small example.
What the workflow protects, and what still requires trust
The practical benefit is that the secret does not need to appear in a prompt, source file, HTTP command argument or normal tool output. The local process still obtains it in memory, and the destination service receives it over HTTPS. Authentication requires that handoff.
If the assistant can execute any command as your user, read Keychain items, or modify the helper and registry, it may be able to bypass this workflow. “Do not show the token” and a SKILL.md file are not technical enforcement. A stronger boundary requires a separate restricted executor and control over permitted operations.
Limit the token's permissions as well. A profile check needs no release or administrative access. File mode 0600 restricts other ordinary users; it does not protect a file from another process running as its owner or from an administrator.
Step 1. Prepare the example and a narrowly scoped token
Download the helper, skill, registry and tests · Archive SHA-256. The archive contains readable source code with no live endpoints or credentials. Review it first; run the commands below from the extracted directory.
The example supports two operations: checking whether an item exists and making GET /api/v4/user against GitLab. For the second, create a personal access token in your GitLab instance with the read_user scope and a suitable expiry date. That scope permits reading user profile information; it is not intended for reading pipelines or publishing releases. GitLab's scope reference.
Create the token yourself in the service's interface. Do not send it to the assistant. You will enter it through a hidden prompt in your local terminal later.
python3 --version
python3 -m unittest discover -s tests -v
The tests use invented values and replace Keychain and HTTP calls. They test the helper's behavior without creating credentials or contacting your GitLab.
Step 2. Describe the target without secret values
In registry.example.json, replace the demonstration HTTPS origin with your GitLab origin. This example supports GitLab at the domain root on the standard port 443. The origin must not contain a username, password, token, query string or API path.
{
"version": 1,
"targets": [
{
"project": "demo-web",
"environment": "dev",
"resource": "gitlab",
"origin": "https://gitlab.example.com",
"keychain_service": "agent-access.demo-web.dev.gitlab",
"keychain_account": "read-user"
}
]
}
demo-web, dev and gitlab are teaching identifiers. If you change them, update keychain_service and the commands consistently. read-user is an item label, not your GitLab login. Similarly, a dev label does not constrain the credential's remote permissions: GitLab controls those.
mkdir -p "$HOME/.config/agent-access"
chmod 700 "$HOME/.config/agent-access"
test ! -e "$HOME/.config/agent-access/registry.json" && \
install -m 600 registry.example.json \
"$HOME/.config/agent-access/registry.json"
If a registry already exists, the final command will leave it in place. Review the current configuration and add your target manually without introducing duplicates. The registry stays outside the repository: it contains no secret values, but real endpoints and resource names can still be internal information.
Step 3. Store the token through a hidden Keychain prompt
Open a normal local terminal without session recording and run the following yourself. The built-in security help add-generic-password instructions recommend placing -w last to obtain a prompt rather than passing the value on the command line.
/usr/bin/security default-keychain -d user
/usr/bin/security add-generic-password \
-s agent-access.demo-web.dev.gitlab \
-a read-user \
-T "" \
-w
The first command shows the default user keychain. The second creates an item there and asks for a value without displaying it. Append nothing after -w: enter the token at the prompt. There is no -U, so the command will not silently replace an existing item. -T "" removes the automatic trust granted to the application creating the item.
macOS may ask for approval when the item is subsequently read. The Mac's owner decides in that system dialog. One-time permission and permanent trust have different consequences; permanently allowing a general tool such as /usr/bin/security does not restrict that trust to one skill. Do not enable access for every application just to remove the prompt. Apple's explanation of application access to Keychain.
Do not manually retrieve and print the value “to check it.” Use the separate presence and authentication checks below.
Step 4. Check access through the helper
python3 keychain-gitlab-read/scripts/gitlab_read.py \
status demo-web dev gitlab
python3 keychain-gitlab-read/scripts/gitlab_read.py \
check demo-web dev gitlab
status looks up item metadata without requesting its value. check obtains the token inside the process, makes a fixed HTTPS request and emits a short result. For example:
{"target":"demo-web/dev/gitlab","origin":"https://gitlab.example.com","operation":"check"}
{"status":"authenticated"}
The origin shown here is deliberately fictional: a real check refuses to use it until you configure your service. The first line describes the target before execution; the second appears only after a successful check of your own service.
gitlab_read.py accepts no arbitrary URL, shell command, write method or “show token” operation. It uses standard TLS verification and rejects redirects. The token passes from captured system-tool output into the helper's memory and then into a request header. API response bodies, headers and exceptions containing request details are not printed.
A small command is easier to inspect as a first example than a general secret exec ... facility. If another tool needs an environment variable, remember that the receiving process will have the secret and may expose it in diagnostics. Review that tool's logging separately.
Step 5. Connect the skill to your AI assistant
The archive includes a ready-to-use keychain-gitlab-read directory. Current Codex documentation places user skills in ~/.agents/skills and repository skills in a project's .agents/skills directory. Check which location your installed assistant version supports. Skill documentation.
mkdir -p "$HOME/.agents/skills"
test ! -e "$HOME/.agents/skills/keychain-gitlab-read" && \
cp -R keychain-gitlab-read "$HOME/.agents/skills/"
Confirm that the skill appears after installation; start a new session if needed. You can invoke it explicitly:
Use $keychain-gitlab-read.
Check authentication for demo-web / dev / gitlab.
Return only the target and status, without the token value.
SKILL.md describes when to select the helper, which commands to run and how to interpret failures. Code performs the check. Repository-wide conventions can go in AGENTS.md; the registry and secrets do not belong there. Instructions through AGENTS.md.
Other AI assistants may use a different integration format. Preserve the same boundaries: a local invocation of a reviewed helper, an exact target, a limited operation and a small result. If MCP is the available interface, expose a specific checking operation rather than a method that returns arbitrary secrets.
Interpreting results without confusing a denial with absence
| Result | What it establishes | Next action |
|---|---|---|
present | Item metadata was found. | Run an authorized check if needed; the value may still be inaccessible or expired. |
not_found | No matching item was found in the selected keychain. | Manually verify the keychain, service and account. Do not blindly create a duplicate. |
unavailable / keychain_unavailable | The check or read could not complete. | Investigate the local session, lock state and system permission. Do not treat the item as lost. |
authenticated | The fixed profile request succeeded. | Do not infer repository, CI or release permissions. |
gitlab_http_401 / gitlab_http_403 | The API rejected the request. | Check expiry, scope and destination without printing the token. |
redirect_refused | The service returned a redirect. | Inspect the endpoint and service configuration; do not automatically forward the secret elsewhere. |
The generic request_failed result intentionally omits request details. Start by checking DNS, the Mac's clock, the HTTPS certificate and service availability without a credential. Do not disable certificate verification to make the check pass.
A prompt for adapting the workflow to your work
If you need another API or stronger project binding, give your assistant the requirements and the example source. Designing the integration does not require secret values.
Adapt this local access workflow for my AI assistant on macOS.
Read the example first and clarify any missing target information:
project, environment, resource and permitted operation.
Keep secret values in Keychain and references to the items
in a separate registry outside the repository.
Build a helper with exact target selection and limited operations.
For repository work, verify the Git root and a credential-free origin.
Do not accept arbitrary URLs, shell commands or secret-display requests.
Read credentials inside the local process; exclude them from command
arguments, logs, reports and results returned to the model.
Verify TLS and do not forward authorization through redirects.
Prepare SKILL.md, installation steps and tests using invented values:
wrong environment, duplicate target, Keychain denial, redirects,
API errors and absence of secrets from output.
For initial entry, give me a hidden prompt in my local terminal.
Do not request a token in chat. Begin with a read operation;
additional write operations need their own agreed scope.
Test more than the successful path after adaptation. An unknown environment should stop before Keychain is read. An API failure should not dump request headers. If a test server reflects the supplied credential in its response, the helper should not pass it to the model.
Extending the setup: Git, servers and token rotation
For HTTPS Git, use a credential helper; for SSH, use keys and an SSH agent. The generic-password example here is for an API token. Database credentials, issue-reading tokens and release permissions should be separate resources where their uses differ. The registry helps select them but cannot replace permissions enforced by the service.
During rotation, identify the item by its exact target, update it through hidden input and test the required operation. If the service allows overlapping tokens, retire the previous one after validating the replacement. If a token has leaked, revoke it immediately even if that interrupts work. Deleting a message or local file does not revoke a credential.
Remote servers and CI need their own secret-delivery mechanism. Unattended work should not depend on a developer Mac's login keychain remaining accessible. This article addresses a narrower problem: convenient local assistant work without routinely copying credentials into conversations.
For the surrounding development and release workflow, see the article on GitLab, OrbStack and AI agents.
What was checked in the published example
The helper passed 11 automated checks covering target selection, registry permissions, keeping secrets out of arguments and output, refusal of HTTP and redirects, Keychain error handling and filtering the API result. The skill format passed validation. The hidden-input command was checked against macOS's built-in help.
No live credentials were copied into this public example. Tests replace Keychain and the network; readers must verify authentication against their own GitLab after setup. This is a small, adaptable source example, with no claim of isolation from arbitrary code running as the same user.
