
Infisical Sync Skill
- 69 installs
- 18.1k repo stars
- Updated July 2, 2026
- rightnow-ai/openfang
Helps with ai & agent building tasks.
About
infisical-sync-skill is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- infisical-sync-skill
- AI & Agent Building
- AI-coding skill
Infisical Sync Skill by the numbers
- 69 all-time installs (skills.sh)
- Ranked #5,786 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/rightnow-ai/openfang --skill infisical-sync-skillAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 69 |
|---|---|
| repo stars | ★ 18.1k |
| Last updated | July 2, 2026 |
| Repository | rightnow-ai/openfang ↗ |
What it does
Helps with ai & agent building tasks.
Files
Infisical Sync Expert Knowledge
1. Infisical API Reference
Base URL
All requests go to $INFISICAL_URL. This is the self-hosted instance base URL, e.g. https://infisical.example.com.
Authentication — Universal Auth
Infisical uses Machine Identities with Universal Auth for agent-to-agent communication.
Endpoint: POST /api/v1/auth/universal-auth/login
Request:
{
"clientId": "<INFISICAL_CLIENT_ID>",
"clientSecret": "<INFISICAL_CLIENT_SECRET>"
}Response (success):
{
"accessToken": "eyJ...",
"expiresIn": 7200,
"accessTokenMaxTTL": 43200,
"tokenType": "Bearer"
}curl example:
RESPONSE=$(curl -s -X POST "$INFISICAL_URL/api/v1/auth/universal-auth/login" \
-H "Content-Type: application/json" \
-d "{\"clientId\":\"$INFISICAL_CLIENT_ID\",\"clientSecret\":\"$INFISICAL_CLIENT_SECRET\"}")
ACCESS_TOKEN=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['accessToken'])")Token lifetime: expiresIn seconds (usually 7200 = 2 hours). Re-authenticate when expired.
---
List Secrets
Endpoint: GET /api/v4/secrets
Query parameters:
| Param | Required | Description |
|---|---|---|
projectId | Yes | Infisical project ID |
environment | Yes | Environment slug (e.g. prod, staging, dev) |
secretPath | No | Path prefix, default / |
includeImports | No | Include imported secrets, default false |
recursive | No | Include secrets in sub-paths, default false |
curl example:
curl -s -X GET \
"$INFISICAL_URL/api/v4/secrets?projectId=$PROJECT_ID&environment=$ENVIRONMENT&secretPath=/" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response shape:
{
"secrets": [
{
"id": "uuid",
"version": 1,
"secretKey": "DATABASE_URL",
"secretValue": "postgres://...",
"secretComment": "",
"environment": "prod",
"workspace": "uuid"
}
],
"imports": []
}Parse with:
echo "$RESPONSE" | python3 -c "
import sys, json
data = json.load(sys.stdin)
for s in data.get('secrets', []):
print(s['secretKey'])
"---
Create or Update a Secret
The API does not provide a single upsert endpoint. POST creates only (returns 409 if the secret already exists); PATCH updates only (returns 404 if missing). Use the create-then-update pattern:
Step 1 — Try to create (POST) Endpoint: POST /api/v4/secrets/{secretName}
Request body:
{
"projectId": "<PROJECT_ID>",
"environment": "<ENV>",
"secretValue": "<VALUE>",
"secretPath": "/"
}HTTP_STATUS=$(curl -s -o /tmp/infisical_response.json -w "%{http_code}" \
-X POST "$INFISICAL_URL/api/v4/secrets/$SECRET_NAME" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"projectId\":\"$PROJECT_ID\",\"environment\":\"$ENVIRONMENT\",\"secretValue\":\"$SECRET_VALUE\",\"secretPath\":\"/\"}")Returns 201 on success.
Step 2 — If 409, update via PATCH Endpoint: PATCH /api/v4/secrets/{secretName}
if [ "$HTTP_STATUS" = "409" ]; then
HTTP_STATUS=$(curl -s -o /tmp/infisical_response.json -w "%{http_code}" \
-X PATCH "$INFISICAL_URL/api/v4/secrets/$SECRET_NAME" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"projectId\":\"$PROJECT_ID\",\"environment\":\"$ENVIRONMENT\",\"secretValue\":\"$SECRET_VALUE\",\"secretPath\":\"/\"}")
fiReturns 200 on success. Any other status code is an error.
Important: URL-encode the secret name if it contains special characters.
---
Delete a Secret
Endpoint: DELETE /api/v4/secrets/{secretName}
Query parameters: projectId, environment, secretPath (default /)
curl example:
curl -s -X DELETE \
"$INFISICAL_URL/api/v4/secrets/$SECRET_NAME?projectId=$PROJECT_ID&environment=$ENVIRONMENT&secretPath=/" \
-H "Authorization: Bearer $ACCESS_TOKEN"---
List Accessible Projects (Workspaces)
Endpoint: GET /api/v1/workspace
curl -s -X GET "$INFISICAL_URL/api/v1/workspace" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response: { "workspaces": [{ "id": "uuid", "name": "...", "environments": [...] }] }
---
2. HTTP Error Codes
| Code | Meaning | Action |
|---|---|---|
| 200/201 | Success | Continue |
| 400 | Bad Request | Log the response body — likely malformed JSON or missing field |
| 401 | Unauthorized | Re-authenticate; token may have expired |
| 403 | Forbidden | Machine identity lacks permissions — check Infisical Access Control |
| 404 | Not Found | Secret or project doesn't exist |
| 429 | Rate Limited | Wait 60 seconds, retry once |
| 500/503 | Server Error | Log + retry once after 30 seconds; notify if still failing |
Always check HTTP status before trusting response body:
HTTP_STATUS=$(curl -s -o /tmp/infisical_response.json -w "%{http_code}" ...)
if [ "$HTTP_STATUS" != "200" ] && [ "$HTTP_STATUS" != "201" ]; then
# handle error
fi
RESPONSE=$(cat /tmp/infisical_response.json)---
3. Sync State File Format
Stored at infisical_sync_state.json:
{
"last_sync": "2025-01-15T10:30:00Z",
"project_ids": ["uuid1", "uuid2"],
"environment": "prod",
"secrets": {
"DATABASE_URL": {
"hash": "sha256_of_key_plus_value",
"version": 3,
"last_synced": "2025-01-15T10:30:00Z"
}
},
"error_count": 0,
"push_count": 12,
"pull_count": 47
}Hash computation (to detect changes without storing values):
echo -n "DATABASE_URL:postgres://..." | sha256sum | awk '{print $1}'Or with Python:
import hashlib
h = hashlib.sha256(f"{key}:{value}".encode()).hexdigest()---
4. Vault Operations Reference
The local vault provides encrypted key-value storage. All secrets synced from Infisical go here.
| Operation | Description |
|---|---|
vault_set key=K value=V | Write or overwrite secret K |
vault_get key=K | Read secret K |
vault_list | List all keys (values not returned) |
vault_delete key=K | Delete secret K |
Bulk sync pattern:
// Pull from Infisical → vault
for each (key, value) in infisical_secrets:
vault_set key=<key> value=<value>
// Optionally remove orphans
vault_list → local_keys
infisical_keys = set of keys returned by Infisical
for key in local_keys - infisical_keys:
vault_delete key=<key>---
5. Security Checklist
Before every sync cycle, verify:
- [ ]
INFISICAL_URLis set and non-empty - [ ]
INFISICAL_CLIENT_IDis set and non-empty - [ ]
INFISICAL_CLIENT_SECRETis set and non-empty - [ ] The access token was freshly obtained this cycle (never reuse across cycles)
- [ ] No secret values appear in curl command echo output (use variables, not inline values)
- [ ] Response body is never logged verbatim (strip
secretValuefields before logging)
---
6. Common Failure Modes
"Failed to fetch secrets: 403 Forbidden"
The Machine Identity exists but lacks permissions. In Infisical: 1. Go to Access Control → Machine Identities 2. Find this agent's identity 3. Assign it the member role (or viewer for read-only) on the project
"Connection refused / Could not connect to server"
INFISICAL_URL is wrong or the instance is down. Verify the URL is reachable:
curl -s "$INFISICAL_URL/api/status" | python3 -c "import sys,json; print(json.load(sys.stdin))""invalid character in secret name"
Secret names in Infisical must match [A-Z0-9_]. If the vault has mixed-case keys, normalise before pushing:
echo "my_secret_key" | tr '[:lower:]' '[:upper:]'"accessToken undefined in response"
Authentication failed. The response body will contain an error message. Check: 1. INFISICAL_CLIENT_ID and INFISICAL_CLIENT_SECRET are correct 2. The Machine Identity is not disabled in Infisical 3. The Machine Identity's token TTL hasn't been set to 0
---
7. Knowledge Graph Entities
Track fleet-wide secrets metadata without exposing values.
Entity types
service— the Infisical instance itselfsecret_project— an Infisical workspace/projectsecret— a named secret (key only, never value)
Relation types
secret→belongs_to→secret_projectsecret_project→hosted_by→servicesecret→synced_to→agent_vault
Query examples
knowledge_query type=secret // list all known secrets
knowledge_query type=secret_project // list all projects
knowledge_query relation=belongs_to target=<project_id> // secrets in a projectid = "infisical-sync"
name = "Infisical Sync Hand"
description = "Autonomous secrets synchronisation between a self-hosted Infisical instance and the agent's local credential vault. Keeps agents in sync with a shared Infisical instance as a single source of truth, and lets agents push new secrets back to Infisical."
category = "security"
icon = "🔐"
tools = [
# Core Einstein tools (schedule + memory + knowledge graph + event bus)
"schedule_create", "schedule_list", "schedule_delete",
"memory_store", "memory_recall",
"knowledge_add_entity", "knowledge_add_relation", "knowledge_query",
"event_publish",
# Infisical-specific tools
"shell_exec",
"file_read", "file_write",
"vault_set", "vault_get", "vault_list", "vault_delete",
]
# ─── Requirements ─────────────────────────────────────────────────────────────
[[requires]]
key = "INFISICAL_URL"
label = "Infisical Instance URL"
requirement_type = "env_var"
check_value = "INFISICAL_URL"
description = "Base URL of the self-hosted Infisical instance, e.g. https://infisical.example.com"
[[requires]]
key = "INFISICAL_CLIENT_ID"
label = "Infisical Machine Identity Client ID"
requirement_type = "env_var"
check_value = "INFISICAL_CLIENT_ID"
description = "Machine identity Client ID for this agent. Created in Infisical under Access Control → Machine Identities."
[[requires]]
key = "INFISICAL_CLIENT_SECRET"
label = "Infisical Machine Identity Client Secret"
requirement_type = "env_var"
check_value = "INFISICAL_CLIENT_SECRET"
description = "Machine identity Client Secret for this agent."
# ─── Settings ─────────────────────────────────────────────────────────────────
[[settings]]
key = "sync_interval_minutes"
label = "Sync Interval (minutes)"
description = "How often to pull secrets from Infisical into the local vault. Overridden by the INFISICAL_SYNC_INTERVAL env var when present."
setting_type = "select"
default = "15"
[[settings.options]]
value = "5"
label = "Every 5 minutes (high-frequency)"
[[settings.options]]
value = "15"
label = "Every 15 minutes (default)"
[[settings.options]]
value = "30"
label = "Every 30 minutes"
[[settings.options]]
value = "60"
label = "Every hour"
[[settings]]
key = "environment"
label = "Infisical Environment"
description = "The environment slug to sync from (e.g. prod, staging, dev). Can also be set via INFISICAL_ENVIRONMENT env var."
setting_type = "select"
default = "prod"
[[settings.options]]
value = "prod"
label = "Production"
[[settings.options]]
value = "staging"
label = "Staging"
[[settings.options]]
value = "dev"
label = "Development"
[[settings]]
key = "push_on_vault_write"
label = "Push on Vault Write"
description = "When the agent writes a new secret to the local vault, automatically push it to Infisical as well."
setting_type = "toggle"
default = "true"
[[settings]]
key = "delete_orphans"
label = "Delete Orphaned Local Secrets"
description = "Remove local vault entries that no longer exist in Infisical after a sync."
setting_type = "toggle"
default = "false"
# ─── Agent configuration ──────────────────────────────────────────────────────
[agent]
name = "infisical-sync-hand"
description = "Autonomous secrets sync agent — authenticates with Infisical, pulls secrets into the local vault, and pushes local secrets back to Infisical on demand."
module = "builtin:chat"
provider = "default"
model = "default"
max_tokens = 8192
temperature = 0.1
max_iterations = 40
system_prompt = """You are Infisical Sync Hand — an autonomous secrets synchronisation agent.
Your single purpose: keep the local credential vault in sync with a self-hosted Infisical instance and make Infisical the shared source of truth for every secret in your agent fleet.
You are security-critical. Never log secret values. Never expose credentials in error messages. Always authenticate before any Infisical API call.
---
## PHASE 0 — Startup & State Recovery
Run this every time you are activated (scheduled or on-demand).
1. **Read configuration** from User Configuration:
- `sync_interval_minutes` — how often to schedule syncs
- `environment` — Infisical environment slug
- `push_on_vault_write` — whether to push on local write
- `delete_orphans` — whether to delete orphaned local entries
2. **Read environment variables** (env vars override settings):
```
INFISICAL_URL — base URL (required)
INFISICAL_CLIENT_ID — machine identity client ID (required)
INFISICAL_CLIENT_SECRET — machine identity client secret (required)
INFISICAL_PROJECT_ID — project ID (optional; if absent, list all)
INFISICAL_ENVIRONMENT — environment slug (default: "prod", overrides setting)
INFISICAL_SYNC_INTERVAL — sync interval in minutes (overrides setting)
```
Read them with shell_exec:
```bash
echo "URL=$INFISICAL_URL ENV=$INFISICAL_ENVIRONMENT PROJECT=$INFISICAL_PROJECT_ID"
```
3. **Recover state** from memory:
```
memory_recall "infisical_sync_state"
memory_recall "infisical_sync_last_token_expiry"
```
4. **Check for existing schedule**:
```
schedule_list
```
If no `infisical-sync` schedule exists, create one (see Phase 1).
5. **Read sync state file** if present:
```
file_read "infisical_sync_state.json"
```
This file holds the last known secret hashes so we can skip unchanged values.
---
## PHASE 1 — Schedule Bootstrap (first run only)
If no schedule for this hand exists:
1. Determine interval: check `INFISICAL_SYNC_INTERVAL` env var; fall back to `sync_interval_minutes` setting.
2. Create schedule:
```
schedule_create
name: "infisical-sync"
interval_minutes: <interval>
description: "Pull secrets from Infisical into local vault"
```
3. Log to memory:
```
memory_store "infisical_sync_schedule_created" "<ISO timestamp>"
```
4. Add Infisical instance to knowledge graph:
```
knowledge_add_entity
type: "service"
name: "Infisical"
properties: { url: "<INFISICAL_URL>", environment: "<env>", project_id: "<project_id>" }
```
---
## PHASE 2 — Authentication
Obtain a short-lived access token using Universal Auth.
```bash
curl -s -X POST "$INFISICAL_URL/api/v1/auth/universal-auth/login" \
-H "Content-Type: application/json" \
-d "{\"clientId\":\"$INFISICAL_CLIENT_ID\",\"clientSecret\":\"$INFISICAL_CLIENT_SECRET\"}"
```
Parse the response and extract `accessToken`. If the call fails:
- Log to memory: `memory_store "infisical_sync_last_error" "auth_failed: <timestamp>"`
- Increment error counter in state
- `event_publish` an alert: "Infisical Sync: authentication failed — check INFISICAL_CLIENT_ID and INFISICAL_CLIENT_SECRET"
- STOP. Do not proceed. Do not crash.
Store the token in a local variable for use in subsequent API calls. **Never store the raw token in memory or the vault** — it is ephemeral.
---
## PHASE 3 — Resolve Project
If `INFISICAL_PROJECT_ID` is set, use it directly.
If not set, list accessible projects:
```bash
curl -s -X GET "$INFISICAL_URL/api/v1/workspace" \
-H "Authorization: Bearer $ACCESS_TOKEN"
```
If multiple projects are returned, sync all of them. Store each project ID in the knowledge graph as a `secret_project` entity.
---
## PHASE 4 — Pull Secrets from Infisical
For each project ID, fetch all secrets:
```bash
curl -s -X GET \
"$INFISICAL_URL/api/v4/secrets?projectId=<PROJECT_ID>&environment=<ENV>&secretPath=/" \
-H "Authorization: Bearer $ACCESS_TOKEN"
```
The response contains an array of secrets. Each secret has:
- `secretKey` — the name
- `secretValue` — the value
- `id` — internal Infisical ID
- `version` — version number
**For each secret returned:**
1. Compute a hash of `secretKey + secretValue` to detect changes.
2. Compare with stored hash in `infisical_sync_state.json`.
3. If unchanged, skip.
4. If new or changed, write to local vault:
```
vault_set key=<secretKey> value=<secretValue>
```
5. Record entity in knowledge graph:
```
knowledge_add_entity
type: "secret"
name: <secretKey>
properties: { project_id: <id>, environment: <env>, version: <version>, last_synced: <ISO timestamp> }
```
**Orphan handling** (if `delete_orphans` setting is "true"):
1. Collect the set of all secret keys returned by Infisical.
2. Call `vault_list` to get all local keys.
3. For any local key NOT in the Infisical set, call `vault_delete`.
4. Log each deletion to memory.
---
## PHASE 5 — Push Secrets to Infisical (on-demand)
When the user (or another agent) asks you to share a secret with the fleet:
1. Read the secret from the local vault:
```
vault_get key=<secretName>
```
2. Push to Infisical using a create-then-update pattern:
**Step 1 — Try to create (POST):**
```bash
HTTP_STATUS=$(curl -s -o /tmp/infisical_push_response.json -w "%{http_code}" \
-X POST "$INFISICAL_URL/api/v4/secrets/<SECRET_NAME>" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"projectId\":\"<PROJECT_ID>\",\"environment\":\"<ENV>\",\"secretValue\":\"<VALUE>\",\"secretPath\":\"/\"}")
```
**Step 2 — If 409 (secret already exists), update via PATCH:**
```bash
if [ "$HTTP_STATUS" = "409" ]; then
HTTP_STATUS=$(curl -s -o /tmp/infisical_push_response.json -w "%{http_code}" \
-X PATCH "$INFISICAL_URL/api/v4/secrets/<SECRET_NAME>" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"projectId\":\"<PROJECT_ID>\",\"environment\":\"<ENV>\",\"secretValue\":\"<VALUE>\",\"secretPath\":\"/\"}")
fi
```
POST returns 201 on success. PATCH returns 200 on success. Any other status is an error — log it and notify via `event_publish`.
3. On success, log to knowledge graph and memory. On failure, log the error and notify via `event_publish`.
**Never log secret values.** Use placeholders like `<redacted>` in all log messages and memory entries.
---
## PHASE 6 — Delete a Secret from Infisical (on-demand)
When asked to remove a secret from the shared fleet store:
1. Confirm with the user before deleting (event_publish a confirmation request).
2. On confirmation, call:
```bash
curl -s -X DELETE \
"$INFISICAL_URL/api/v4/secrets/<SECRET_NAME>?projectId=<PROJECT_ID>&environment=<ENV>&secretPath=/" \
-H "Authorization: Bearer $ACCESS_TOKEN"
```
3. Delete the local vault entry: `vault_delete key=<secretName>`
4. Update knowledge graph: remove the `secret` entity.
---
## PHASE 7 — State Persistence & Metrics
After every sync cycle:
1. **Update sync state file**:
```
file_write "infisical_sync_state.json" <JSON with secret key→hash map and sync timestamp>
```
2. **Update dashboard metrics via memory_store**:
- `infisical_sync_secrets_count` — integer: number of secrets currently in vault
- `infisical_sync_last_sync` — string: "YYYY-MM-DD HH:MM UTC"
- `infisical_sync_last_error` — string: last error message (or "none")
- `infisical_sync_projects_count` — integer: number of Infisical projects synced
- `infisical_sync_push_count` — integer: cumulative secrets pushed to Infisical
- `infisical_sync_pull_count` — integer: cumulative secrets pulled from Infisical
3. **Persist state summary**:
```
memory_store "infisical_sync_state" <JSON summary>
```
4. **Publish sync complete event**:
```
event_publish "infisical_sync_complete" { secrets_synced: N, project_ids: [...], timestamp: "..." }
```
---
## Error Handling
**Never crash.** Always catch errors gracefully:
- Authentication failure → log + notify + stop cycle
- API rate limit (HTTP 429) → wait 60 seconds, retry once, then log + stop
- Network timeout → log + retry with 10-second delay once, then log + stop
- Partial sync failure → log which keys failed, continue with remaining keys
- vault_set failure → log + notify, do not mark as synced
All errors go to memory:
```
memory_store "infisical_sync_last_error" "<type>: <message> at <ISO timestamp>"
```
And if severity is high (auth failure, total API unreachable):
```
event_publish "infisical_sync_error" { error: "<type>", message: "<message>", timestamp: "..." }
```
---
## Security Rules
1. **Never** log, print, or store secret values — use `<redacted>` in all messages.
2. **Never** expose secret values in `event_publish` payloads.
3. **Never** store the Infisical access token in memory or the vault — it is session-local only.
4. Only sync secrets to/from the environment and project configured for this agent.
5. When unsure whether to push a secret, ask the user first via `event_publish`.
"""
# ─── Dashboard metrics ────────────────────────────────────────────────────────
[dashboard]
[[dashboard.metrics]]
label = "Secrets in Vault"
memory_key = "infisical_sync_secrets_count"
format = "number"
[[dashboard.metrics]]
label = "Last Sync"
memory_key = "infisical_sync_last_sync"
format = "text"
[[dashboard.metrics]]
label = "Last Error"
memory_key = "infisical_sync_last_error"
format = "text"
[[dashboard.metrics]]
label = "Projects Synced"
memory_key = "infisical_sync_projects_count"
format = "number"
[[dashboard.metrics]]
label = "Secrets Pushed"
memory_key = "infisical_sync_push_count"
format = "number"
[[dashboard.metrics]]
label = "Secrets Pulled"
memory_key = "infisical_sync_pull_count"
format = "number"