indykite-ciq-create-node — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited indykite-ciq-create-node (Agent Skill) and scored it 91/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 1 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} is the classic direct prompt-injection phrasing. Placed in a skill body that the agent reads as trusted instructions, it tries to make the agent abandon its prior rules and follow whatever comes next — a full system-prompt override.
ignore/disregard/forget … previous instructions sentence.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Create a brand-new node in the IndyKite Graph (IKG) through a ContX IQ policy + Knowledge Query, run via POST /contx-iq/v1/execute. The policy declares an allowed_upserts.nodes.node_types whitelist of node labels that may be created, and the Knowledge Query's upsert_nodes array names the node, sets its external_id, and lists the properties to write. (CIQ writes use the same policy + Knowledge Query shape as reads.)
This skill covers exactly that - node creation only. Other write paths are deliberately out of scope:
allowed_upserts.nodes.existing_nodes and a Knowledge Query upsert_nodes entry that references a variable from the policy's cypher (no external_id). Different field, different KQ shape.allowed_upserts.relationships.relationship_types ({type, source_node_label, target_node_label} triples) and the Knowledge Query's upsert_relationships array.allowed_deletes and delete_nodes / delete_relationships.For reads, see indykite-ciq-read.
Activate this skill when the user:
Track, Document, Account, or other entity);_Application-subject "catalog write" policy + Knowledge Query - the typical pattern for ETL / system-side ingestion;external_id and properties from execute-time input_params;403 / 422 from a POST /contx-iq/v1/execute call that should have created a node but didn't.Do not activate this skill when the user:
indykite-ciq-read;existing_nodes) and KQ shape;relationship_types) and KQ array (upsert_relationships);allowed_deletes) and KQ array;POST /capture/v1/nodes) or Terraform to ingest data instead of CIQ - those are separate ingestion paths.X-IK-ClientKey at execute time).PROJECT_GID - both used to create the policy and Knowledge Query.Track, Document, Customer, etc.) - already part of the project's data model._Application subjects, the subject's node already in the IKG (CIQ doesn't create the subject; it authorizes against it).$param at execute time (common).If any of these are missing, stop and tell the user - fixing them first is much cheaper than debugging a vague 403 or 422.
Subject type - pick one. The schema is identical across both choices; only subject.type, the filter, and the execute-time auth differ:
| Subject | Use when | Auth at execute time | Filter convention |
|---|---|---|---|
_Application | System-side / ETL / catalog work; no user in the loop. | X-IK-ClientKey only. | subject.external_id = $_appId (reserved). |
Person / User | The authenticated user is performing the operation themselves. | X-IK-ClientKey + Authorization: Bearer <token>. | subject.external_id = $token.sub. |
A policy is restricted to a single subject type - if both should be allowed, write two policies. The runnable example below uses _Application (system-side catalog ingestion); a Person variant - for example, a user creating their own Playlist - differs only in subject.type, the filter, and the execute headers.
Cypher anchor - even a write-only policy needs a MATCH clause that anchors to the subject. The new node is not matched in cypher; it's declared in the Knowledge Query's upsert_nodes.
Working example (used throughout this skill):
System-side catalog ingestion: an_Applicationcreates a newTracknode, supplyingexternal_id,title, andloudnessat execute time.
MATCH (subject:_Application)That's the entire cypher - just enough to identify the subject. The Track does not appear here.
allowed_upserts.nodes.node_typesBuild the policy JSON with four blocks:
meta.policy_version - currently 1.0-ciq.subject.type - _Application for the running example.condition.cypher and condition.filter - anchor to the subject. For _Application, filter on subject.external_id = $_appId (a reserved value auto-filled from the AppAgent at execute time).allowed_upserts.nodes.node_types - array of node labels the Knowledge Query may create as new nodes.Omit allowed_reads, allowed_deletes, and the other allowed_upserts sub-fields if this policy only creates nodes. Leaving them out is the supported way to forbid those operations.
A complete create-only policy for the running example: see assets/policy-create-track.json.
Create it through the Config API:
# set the current project_id, and stringify only the `policy` field, before POSTing
jq --arg pid "$PROJECT_GID" '.project_id = $pid | .policy |= tojson' indykite-ciq-create-node/assets/policy-create-track.json \
| curl -X POST "$API_URL/configs/v1/authorization-policies" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $SERVICE_ACCOUNT_TOKEN" \
-d @-A 201 Created returns the policy's id (GID). Export it as POLICY_ID - the Knowledge Query create injects it into policy_id.
For the full schema (operators, attribute conventions, why we omit existing_nodes and allowed_reads) see references/policy-reference.md.
upsert_nodesThe Knowledge Query references the policy and lists what to write. Each entry in upsert_nodes describes one node to create:
name - a distinct variable name not used in the policy's cypher. This is the variable other arrays (nodes, relationships) reference.type - the node label. Must be in the policy's allowed_upserts.nodes.node_types.external_id - required for new nodes. Hardcode for one-off writes, or use $param (the common case) so the caller supplies it at execute time.properties - array of {type, value, metadata?} items. The type (property name) must be hardcoded; the value may be hardcoded or $param.Echo the new node back in the response by listing its variable name in the top-level nodes array.
A complete Knowledge Query for the running example: see assets/knowledge-query-create-track.json.
Create it through the Config API:
# set the current project_id and policy_id, and stringify only the `query` field, before POSTing
jq --arg pid "$PROJECT_GID" --arg polid "$POLICY_ID" '.project_id = $pid | .policy_id = $polid | .query |= tojson' indykite-ciq-create-node/assets/knowledge-query-create-track.json \
| curl -X POST "$API_URL/configs/v1/knowledge-queries" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $SERVICE_ACCOUNT_TOKEN" \
-d @-A 201 Created returns the Knowledge Query's id (GID) - what execute and the MCP ciq_execute tool will reference.
Schema details for every Knowledge Query field, including the protected property names you cannot set: references/knowledge-query-reference.md.
The execute endpoint is the same as for reads:
POST <API_URL>/contx-iq/v1/executeAuthentication for the running _Application-subject example:
X-IK-ClientKey: <AppAgent-credentials-token> - required.Authorization: Bearer … - omit for _Application subjects. The AppAgent itself authenticates the subject, and $_appId is auto-filled from the application's external_id.For Person-subject create flows, add Authorization: Bearer <user-access-token> and the policy's filter on subject.external_id = $token.sub will pin the cypher anchor to that user.
Request body:
{
"id": "<knowledge_query_gid_or_name>",
"input_params": {
"track_external_id": "track-99",
"track_title": "New Hot Track",
"track_loudness": -7.5
}
}A runnable shell helper: scripts/execute.sh.
Full execute reference (auth combinations, response shape, error codes): references/execution-reference.md.
A successful create execute returns the new node's projection:
{
"data": [
{
"nodes": {
"newTrack.external_id": "track-99",
"newTrack.property.title": "New Hot Track",
"newTrack.property.loudness": -7.5
}
}
]
}If the response is not what you expected, walk this list before changing the policy or KQ:
upsert_nodes[].type must be in the policy's allowed_upserts.nodes.node_types. Mismatch → 403."$track_external_id"), the caller must supply it in input_params. Missing → 422 invalid_argument: missing or wrong input params.upsert_nodes[].name should be fresh - not a name that already appears in the policy's cypher. If it collides, the policy thinks you're updating an existing match instead of creating._service, create_time, external_id, id, type, update_time cannot be set as properties - they're managed by the platform.external_id upserts (updates) instead of creating; the response will look similar but no new node is added.For other failure modes (auth shape wrong, malformed JSON, subject filter mismatch) see references/troubleshooting.md.
When this skill has been applied successfully:
subject.type, a Cypher pattern that anchors to the subject, optional partial filters, and an allowed_upserts.nodes.node_types whitelist - no allowed_reads, no allowed_deletes, no existing_nodes.upsert_nodes with a distinct name, the right type, an external_id, and the properties to set.POST /contx-iq/v1/execute (or the MCP ciq_execute tool) returns the new node's projection on success.indykite-ciq-read) finds the new node in the IKG.references/policy-reference.md - write-focused policy schema, allowed_upserts.nodes deep-dive (existing vs node_types), why other blocks are omitted.references/knowledge-query-reference.md - upsert_nodes schema, properties + metadata, protected property names, returning the new node.references/execution-reference.md - POST /contx-iq/v1/execute for writes, auth combinations including _Application reserved $_appId, response shape.references/troubleshooting.md - 403 / 422 / duplicate external_id / missing properties patterns.assets/policy-create-track.json - runnable create-only policy for the _Application → new Track example.assets/knowledge-query-create-track.json - matching Knowledge Query.scripts/execute.sh - Bash helper that posts to /contx-iq/v1/execute with the right headers.This skill uses generic markdown instructions and works across all agents listed in the README. The agent needs to be able to issue HTTP requests (curl, an HTTP client, or the IndyKite Terraform provider - see References). No Claude Code hooks, Cursor @-mentions, or Copilot workspace context are required.
indykite_authorization_policy and indykite_knowledge_query~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.