Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Pah Link Aap

CSecurity

Point this environment's AAP project syncs at its own Private Automation Hub, so collections resolve from the curated `approved` repository instead of the internet. Mints a read-scoped gateway token from credentials the environment already holds, creates the Galaxy credential, assigns it to the organization, and proves it with a real project sync. Fully reversible. TRIGGER when: the user asks to point AAP at PAH, wants project syncs to resolve from the hub, asks why a populated hub is not bei...

2 stars
0 votes
0 copies
0 views
Added 10/6/2026
devopspythongobashgitapi

Works with

apimcp

Security Analysis

C63/100
criticalSends environment variables or credentials to an external URL
criticalDownloads and executes remote scripts — classic supply chain attack

Pro shows the line behind each finding and how to fix it

Scanned 10/6/2026

$npx -y skills add ericcames/sales.demos --skill pah-link-aap --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Pah Link Aap?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Pah Link Aap
[![Security: C — Skills Directory](https://www.skillsdirectory.com/api/skills/ericcames-pah-link-aap/badge)](https://www.skillsdirectory.com/skills/ericcames-pah-link-aap)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: pah-link-aap
description: "Point this environment's AAP project syncs at its own Private Automation Hub, so collections resolve from the curated `approved` repository instead of the internet. Mints a read-scoped gateway token from credentials the environment already holds, creates the Galaxy credential, assigns it to the organization, and proves it with a real project sync. Fully reversible. TRIGGER when: the user asks to point AAP at PAH, wants project syncs to resolve from the hub, asks why a populated hub is not being used, wants organization Galaxy credentials, asks about issue #69, or wants to undo that link. SKIP: if the hub is empty or has not been populated and curated yet — that is pah-sync — or if the user wants to mirror the execution environment image, which is sales-demos-ee-build."
---

# pah-link-aap

Closes [#69](https://github.com/ericcames/sales.demos/issues/69). The hub is
populated by [`pah-sync`](../pah-sync/SKILL.md) and curated by
`curate_hub.yml`; until this runs, **nothing points at it**.

This skill contains **no logic**. The work is
[`playbooks/link_hub.yml`](../../../playbooks/link_hub.yml).

## What it does, and what it risks

Creates a `Sales Demos - PAH Galaxy` credential aimed at
`https://<aap_hostname>/api/galaxy/content/approved/` and assigns it to the
`IT Service Automation` organization.

**A Galaxy credential on the organization makes EVERY project sync in it resolve
from PAH.** If `approved` is short one collection, the sync fails and every job
template fails with it. Say that out loud before running this in front of anyone.

Three things bound it:

| Bound | Where |
|---|---|
| Refuses to link an empty `approved` | the playbook, before it mints anything |
| Reversal proven, not just written | `-e hub_galaxy_link_state=absent` |
| `sandbox` before `demo` | your discipline, not the code |

## One distribution, not four

`approved` only. Not the three mirrors as fallbacks, and not public Galaxy.

The mirrors' contents are decided by Red Hat and the community; `approved`'s are
declared in `hub/approved-collections.yml`. Pointing at a mirror "just in case"
gives up the only claim this use case makes — *your teams install what you
approved* — in exchange for hiding the failure this playbook is designed to
surface.

## The token is a gateway token, not a PAH API token

**This is the trap, and it cost a failed run.** The obvious mint is the hub's own
API token endpoint, `POST /api/galaxy/v3/auth/token/`. It exists on AAP 2.7 and
issues a perfectly real 40-character token. **The gateway does not accept it.**

Measured on sandbox 2026-09-04, against the hub's collection index:

| Credential | Result |
|---|---|
| basic auth (`admin` / password) | `200` |
| galaxy_ng token, `Authorization: Token` | `403` |
| galaxy_ng token, `Authorization: Bearer` | `403` |
| **gateway token, either scheme** | **`200`** |

A Galaxy credential built from a galaxy_ng token fails the project sync with
`HTTP Code: 403, Message: Authentication credentials were not provided` — which
reads like a credential that was never attached rather than one that was
rejected, and sends you looking in entirely the wrong place.

So the mint is `POST /api/gateway/v1/tokens/`, the same endpoint
[`utilities/make-aap-mcp.sh`](../../../utilities/make-aap-mcp.sh) already uses.
Scope is `read`: a project sync only downloads, and a gateway token inherits the
creating user's permissions.

**Nothing is stored.** The token is minted from `aap_username` / `aap_password`,
which already rotate with the environment, and never written to disk. That is the
answer to #69's gate 3 — the credential dies with the RHDP environment, so the
question was never *where to keep it* but *how to mint it again*.

**Gateway tokens accumulate**, unlike the galaxy_ng ones, so the playbook retires
the tokens it minted on earlier runs before minting a fresh one. Exactly one
should ever exist. The unlink deletes it.

## Preflight Check

```bash
ENV=${ENV:-sandbox}
./utilities/preflight.sh "$ENV"

# The environment answers at all (RHDP envs expire silently)
HOST=$(grep -oP '(?<=^aap_hostname: ")[^"]+' inventory/group_vars/$ENV/connection.yml)
curl -sk --max-time 15 "https://$HOST/api/gateway/v1/ping/" \
  | grep -q '"status":"good"' \
  && echo "✅ $ENV reachable ($HOST)" \
  || echo "❌ $ENV is not answering — check the environment is still alive"

# The curated list is complete (--write-approved computes the transitive closure)
python3 utilities/refresh-hub-requirements.py --write-approved
```

If any check fails, stop and tell the user which one and the fix beside it.

**Then confirm `approved` is populated** — this skill's whole premise:

```bash
./utilities/run-ansible.sh playbooks/curate_hub.yml -i inventory --limit $ENV \
  -e target_env=$ENV \
  --vault-id sales.demos@~/secrets/.vault_pass_sales_demos
```

It is idempotent, so running it here costs nothing and removes the only
interesting failure mode. If it has never run against this environment, it will
report `Added N` — that is expected on a fresh hub, not a warning.

## Dry run first

```bash
./utilities/run-ansible.sh playbooks/link_hub.yml -i inventory --limit sandbox \
  -e target_env=sandbox \
  --vault-id sales.demos@~/secrets/.vault_pass_sales_demos --check
```

`--check` mints nothing and creates nothing, **but still verifies the curated
repository** — the read-only gates carry `check_mode: false` on purpose, so this
is a real preflight rather than a play that skips its own checks.

## Run

```bash
mkdir -p ~/ansible-logs
export ANSIBLE_LOG_PATH=~/ansible-logs/pah-link-sandbox-$(date +%F-%H%M).log

./utilities/run-ansible.sh playbooks/link_hub.yml -i inventory --limit sandbox \
  -e target_env=sandbox \
  --vault-id sales.demos@~/secrets/.vault_pass_sales_demos
```

> **`--limit` is mandatory.** Without it the run targets both environments and
> `tasks/assert_target_environment.yml` fails it.
>
> **Never pipe through `tee`** — it breaks the callback's output handling.

**It ends with a real project sync.** That is not decoration: it is the only step
that proves the hub can actually serve what a project needs. A failure there is
the playbook working, not the playbook broken.

### Reversing it

```bash
./utilities/run-ansible.sh playbooks/link_hub.yml -i inventory --limit sandbox \
  -e target_env=sandbox -e hub_galaxy_link_state=absent \
  --vault-id sales.demos@~/secrets/.vault_pass_sales_demos
```

Unassigns the credential, deletes it, and retires the gateway token. Project
syncs go back to resolving from the execution environment, as they did before
#69. **Run this the moment anything looks wrong** — it is quick and it is proven.

## Verify against AAP, not the recap

```
# The organization carries exactly one Galaxy credential, pointed at `approved`
mcp__aap-<env>__organizations_list
# Find "IT Service Automation" and check its galaxy_credentials

# Credentials — look for the Galaxy credential attached to the organization
mcp__aap-<env>__credentials_list
# Filter for the Galaxy credential type
```

The vault+curl path still works if the MCP server is not registered:

```bash
HOST=$(grep -oP '(?<=^aap_hostname: ")[^"]+' inventory/group_vars/sandbox/connection.yml)
PW=$(ansible-vault view playbooks/group_vars/all/secrets.yml \
       --vault-id sales.demos@~/secrets/.vault_pass_sales_demos \
     | python3 -c 'import sys,yaml; print(yaml.safe_load(sys.stdin)["env_secrets"]["sandbox"]["aap_password"])')

curl -sk -u "admin:$PW" \
  "https://$HOST/api/controller/v2/organizations/?name=IT%20Service%20Automation" \
  | python3 -c 'import sys,json; print(json.load(sys.stdin)["results"][0]["related"]["galaxy_credentials"])'

curl -sk -u "admin:$PW" \
  "https://$HOST/api/gateway/v1/tokens/?description=sales.demos%20PAH%20Galaxy%20(%2369)" \
  | python3 -c 'import sys,json; print(json.load(sys.stdin)["count"], "token(s)")'
```

**The strongest check is the one the playbook already ran**: read the project
update's stdout and confirm `Fetch galaxy collections from
collections/requirements.yml` is `changed` and green. The download URLs in a
verbose run name `/api/galaxy/v3/plugin/ansible/content/approved/` — that is what
"no internet egress" looks like in evidence rather than in prose.

### The clean-container version of the same claim

```bash
EE_TAG=$(sed -n 's|^ *image: *"{{ *aap_hostname *}}/sales_demos_ee:\([^"]*\)".*|\1|p' \
  inventory/group_vars/aap/controller_execution_environments.yml)
EE="quay.io/zigfreed/sales-demos-ee:${EE_TAG}"
podman run --rm -v "$PWD/collections/requirements.yml:/tmp/req.yml:ro,Z" \
  -e T="<a read-scoped gateway token>" -e H="$HOST" "$EE" bash -lc '
    ansible-galaxy collection install -r /tmp/req.yml \
      -s "https://$H/api/galaxy/content/approved/" --api-key "$T" \
      -p /tmp/out --force --ignore-certs'
```

Every artifact should download from `/content/approved/`. This is #69's gate 2
in its literal wording, and it needs a container because a laptop already has the
collections installed and will report "nothing to do".

## If it fails

| Symptom | Cause | Fix |
|---|---|---|
| `'approved' is empty, so pointing AAP at it would break every project sync` | The curated repository was never populated on this environment | Run `curate_hub.yml`, then re-run |
| Project sync: `Could not satisfy the following requirements: * <ns>.<name>` | `approved` is missing a **transitive** dependency | `python3 utilities/refresh-hub-requirements.py --write-approved`, commit, `curate_hub.yml`, re-run. This is exactly how `ansible.eda` was found |
| Project sync: `403 ... Authentication credentials were not provided` | The credential's token is not one the gateway accepts | You are minting a galaxy_ng token. It must be a gateway token — see above |
| Two tokens with the playbook's description | A run was interrupted between the retire and the mint | Delete the older one; the next clean run also fixes it |
| Every job template suddenly fails at project sync | Whatever the cause, do not debug it live | `-e hub_galaxy_link_state=absent`, then investigate |
| `Attempting to decrypt but no vault secrets found` | `--vault-id` missing | Add it; see the Run block |

Never paste a live cluster hostname, password or token into a commit message,
issue, or PR. This repo is public.

Attribution

ericcamesericcames
View sourceSee grades on GitHubMore from ericcames →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Terraform Module Library

Build reusable Terraform modules for AWS, Azure, and GCP infrastructure following infrastructure-as-code best practices. Use when creating infrastructure modules, standardizing cloud provisioning, or implementing reusable IaC components.

401991 votes

sematext-otel

Wire a service's OpenTelemetry output to Sematext Cloud. Walks through region, App-type, instrumentation flow (managed OTLP endpoint vs Sematext Agent), and signal selection (traces/metrics/logs), then produces the exact env-var block and points at a runnable reference example in this repo. Invoke when instrumenting a new app for Sematext.

01 votes

Deployment Patterns

Deployment workflows, CI/CD pipeline patterns, Docker containerization, health checks, rollback strategies, and production readiness checklists for web applications. Use when setting up deployment infrastructure or planning releases.

2699140 votes

Babysit

Watch a pull request or review cycle until it is ready to merge. Use when asked to babysit, monitor, or keep checking PR comments, reviews, and CI until all actionable issues are resolved.

971540 votes

V7 Roster

Interact with the Paperclip control plane API for task coordination and governance. Use when checking assignments, updating issue status, posting comments, delegating work, managing routines, or calling Paperclip API endpoints.

953190 votes
View all in devops →