indykite-ciq-create-relationship — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited indykite-ciq-create-relationship (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 relationship between two nodes that already exist in the IndyKite Graph (IKG), driven by a ContX IQ policy + Knowledge Query and run via POST /contx-iq/v1/execute. The policy declares an allowed_upserts.relationships.relationship_types whitelist of {type, source_node_label, target_node_label} triples and matches the two endpoint nodes in its cypher; the Knowledge Query's upsert_relationships array names the new relationship and references those `cypher` variables as source and target. The endpoint nodes must already exist - only the relationship is created.
This skill covers exactly that - relationship creation between two pre-existing nodes. Other paths are deliberately out of scope:
allowed_upserts.nodes.node_types and upsert_nodes - see indykite-ciq-create-node.upsert_nodes entry for the new node plus an upsert_relationships entry that points its source or target at the fresh name. The schema supports this; this skill keeps the example tight to two existing endpoints for clarity. See "Adapting for a fresh endpoint" near the bottom.allowed_upserts.relationships.existing_relationships.allowed_deletes.relationships.For reads, see indykite-ciq-read.
Activate this skill when the user:
Person -[:ACCEPTED]-> Contract, Track -[:PLAYED_AT]-> Venue, User -[:OWNS]-> Document);_Application-subject "catalog wiring" policy + Knowledge Query for system-side relationship ingestion;external_ids and (optionally) the new relationship's properties from execute-time input_params;403 / 422 from a POST /contx-iq/v1/execute call that should have created a relationship but didn't.Do not activate this skill when the user:
indykite-ciq-create-node;indykite-ciq-read;existing_relationships) and KQ shape;allowed_deletes.relationships) and KQ array;POST /capture/v1/relationships) or Terraform to ingest relationships 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.external_ids. CIQ doesn't seed them; this policy authorises wiring two existing nodes.PLAYED_AT, ACCEPTED, OWNS, etc.) and the source/target node labels it connects. Both must match what the IKG schema already permits._Application subjects, the subject's node also already in the IKG.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 wiring); a Person variant - for example, a user accepting a Contract - differs only in subject.type, the filter, and the execute headers.
Cypher pattern - must MATCH both endpoint nodes, plus the subject. Use disjoint MATCH clauses (separated by spaces) when the endpoints aren't connected through any other path you need.
Working example (used throughout this skill):
Music-dataset domain: an_Applicationadds aPLAYED_ATrelationship from an existingTrackto an existingVenue, given bothexternal_ids.
MATCH (subject:_Application)
MATCH (track:Track)
MATCH (venue:Venue)Variables the rest of the policy and KQ will reference: subject, track, venue. The new relationship does not appear in the cypher - it's declared in the KQ.
If you need to constrain the endpoints to be reachable along an existing path before the new edge can be added, use a connected pattern instead - for example, you might require the Track and Venue to share an existing :CATALOGED_BY relationship before allowing the PLAYED_AT link. That decision belongs in the cypher.
allowed_upserts.relationships.relationship_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 the subject and pin the endpoints by external_id. For _Application, filter on subject.external_id = $_appId (reserved, auto-filled). For each endpoint, filter on its external_id against a $param.allowed_upserts.relationships.relationship_types - array of {type, source_node_label, target_node_label} triples the Knowledge Query may create as new relationships.Omit allowed_reads, allowed_deletes, and the other allowed_upserts sub-fields if this policy only creates relationships. Omitting a block is the supported way to forbid that operation.
A complete relationship-create policy for the running example: see assets/policy-create-played-at.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-relationship/assets/policy-create-played-at.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 (the relationship_types triple, why we omit existing_relationships and node_types) see references/policy-reference.md.
upsert_relationshipsThe Knowledge Query references the policy and lists what to write. Each entry in upsert_relationships describes one new relationship:
name - a distinct variable name not used in the policy's cypher. Convention: prefix with new or use a domain-specific noun. The response uses this name as the key for the new relationship's identifiers.source - the variable name of the source endpoint from the policy's cypher. Must match what relationship_types[].source_node_label declares.target - the variable name of the target endpoint from the policy's cypher. Must match target_node_label.type - the relationship label. Must equal the relationship_types[].type in the policy.properties - optional. Same {type, value, metadata?} shape as node properties - see references/knowledge-query-reference.md.Echo the new relationship back in the response by listing its variable name in the top-level relationships array.
A complete Knowledge Query for the running example: see assets/knowledge-query-create-played-at.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-relationship/assets/knowledge-query-create-played-at.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).
The execute endpoint is the same as for reads and node-creates:
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. The reserved $_appId is auto-filled from the application's external_id.For Person-subject linking flows, add Authorization: Bearer <user-access-token> and the policy's filter on subject.external_id = $token.sub.
Request:
{
"id": "<knowledge_query_gid_or_name>",
"input_params": {
"track_external_id": "track-99",
"venue_external_id": "venue-1"
}
}A runnable shell helper: scripts/execute.sh.
Full execute reference (auth, request/response, error semantics): references/execution-reference.md.
A successful create-relationship execute returns the projected nodes plus a relationships block keyed by the variable name with internal graph identifiers:
{
"data": [
{
"nodes": {
"track.external_id": "track-99",
"venue.external_id": "venue-1"
},
"relationships": {
"newPlayedAt": {
"Id": 1152932499723124700,
"ElementId": "5:3a2b09d5-…:1152932499723124736",
"StartId": 0,
"StartElementId": "4:3a2b09d5-…:0",
"EndId": 15
}
}
}
]
}The Id / ElementId are the platform's internal identifiers for the new edge; you don't need to use them but they confirm the edge was written.
If the response is not what you expected, walk this list before changing the policy or KQ:
track.external_id or venue.external_id isn't seeded, the cypher returns no rows and there's nothing for upsert_relationships to attach to.(source, target, type) must align with the policy's relationship_types[] triple - same labels (via the cypher variables) and same type.source and target are cypher variable names, not labels. If you write "source": "Track" (the label) instead of "source": "track" (the variable), the request fails.name must not collide with an existing variable in the policy's cypher.For other failure modes (auth shape wrong, missing input_params, malformed JSON) see references/troubleshooting.md.
If you need to create the target node and the relationship in one execute - say, create a new Comment and link it to an existing Document - combine this skill's pattern with indykite-ciq-create-node:
allowed_upserts.nodes.node_types (for the new node label) and allowed_upserts.relationships.relationship_types (for the new edge).upsert_nodes entry for the new node, plus an upsert_relationships entry whose source or target is the fresh node's name.The two skills cover the parts; combining is just one extra upsert_* array entry on each side. Each operation must be whitelisted in the policy.
When this skill has been applied successfully:
subject.type, a Cypher pattern matching both endpoint nodes, partial filters pinning them by external_id, and an allowed_upserts.relationships.relationship_types whitelist - no node_types, no existing_relationships, no allowed_reads (unless intentionally added), no allowed_deletes.upsert_relationships with a fresh name, source/target variables from cypher, and the right type.POST /contx-iq/v1/execute returns data with the projected nodes and the new relationship's internal identifiers.indykite-ciq-read) finds the new edge in the IKG.references/policy-reference.md - relationship-create policy schema, the {type, source_node_label, target_node_label} triple, why other blocks are omitted.references/knowledge-query-reference.md - upsert_relationships schema, optional properties, returning the new relationship.references/execution-reference.md - POST /contx-iq/v1/execute for relationship writes, auth combinations, response shape with Id / ElementId / StartId / EndId.references/troubleshooting.md - symptom → cause → fix tables for 403 / 422 / no-match-on-cypher / variable-vs-label confusion.assets/policy-create-played-at.json - runnable _Application → (Track)-[:PLAYED_AT]->(Venue) policy.assets/knowledge-query-create-played-at.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). No Claude Code hooks, Cursor @-mentions, or Copilot workspace context are required.
kqb pattern is the canonical relationship-create variant.policyAllowUpsertRelationships / knowledgeQueryUpsertRelationships pairs in the resource samples.indykite_authorization_policy and indykite_knowledge_query~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.