Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
67cfa82
feat: integrate Arbiter proxy, discovery pipeline, synthetic traffic …
LukasParke Jun 3, 2026
97caf8e
feat: merge PR 114 schema extractions into discovery pipeline
LukasParke Jun 3, 2026
ceb4e29
feat: extract duplicate schemas, fix vacuum errors, add vacuum CI
LukasParke Jun 3, 2026
5dd4899
fix: address CodeRabbit PR review feedback
LukasParke Jun 3, 2026
740c735
Merge branch 'main' into feature/arbiter-discovery-pipeline
LukasParke Jun 3, 2026
4039d7b
feat: add Phase 4 plex.tv v2 account, social, and utility endpoints
LukasParke Jun 3, 2026
c6c6bef
feat: Phase 5 schema depth — 60 new fields, 5 new schemas
LukasParke Jun 3, 2026
eaf7b81
feat: Phase 6 library browse endpoints & query params
LukasParke Jun 3, 2026
11ca6ee
feat: Phase 7 final polish — remove orphans, add x-tagGroups, retries
LukasParke Jun 3, 2026
5dd93d7
feat: Add all remaining gap endpoints — JWT, playback, library, live TV
LukasParke Jun 3, 2026
0d5adbc
feat: Final gap closure — library types, query params, docs, SDK hints
LukasParke Jun 3, 2026
377eb9a
feat: 100% parity — final endpoints, schema fixes, query params
LukasParke Jun 3, 2026
bf0cd13
chore: remove review analysis documents from PR branch
LukasParke Jun 3, 2026
9a99b8f
ci: fix vacuum lint action — pin version, add details, report artifact
LukasParke Jun 4, 2026
8e2e746
ci: use latest vacuum release
LukasParke Jun 4, 2026
8974a67
chore: remove REMAINING_GAPS_PLAN.md from PR branch
LukasParke Jun 4, 2026
6fc7c21
chore: remove all backfill and discovery scripts (migrated to arbiter)
LukasParke Jun 4, 2026
3aa682b
Update discovery workflow to use Arbiter native commands
LukasParke Jun 4, 2026
00231f6
Fix CI: speakeasy and vacuum installation
LukasParke Jun 4, 2026
809923d
Add agent swarm review findings
LukasParke Jun 4, 2026
0dd25a7
Fix all documented spec gaps from agent swarm review
LukasParke Jun 4, 2026
a4b4ba9
fix: Complete schema coverage for all 404 operations
LukasParke Jun 4, 2026
a9a90d4
chore: Remove review markdown files
LukasParke Jun 4, 2026
8dacca0
chore: Remove test-data from repo and gitignore it
LukasParke Jun 4, 2026
2240187
docs: Add comprehensive descriptions, examples, and documentation
LukasParke Jun 4, 2026
809cebb
feat: Add Arazzo 1.0.0 workflow documents
LukasParke Jun 4, 2026
be041fa
feat: Arazzo validation CI workflow with dual validation
LukasParke Jun 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions .github/workflows/arazzo-validation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
name: Arazzo Workflow Validation

on:
push:
branches: [main, feature/arbiter-discovery-pipeline]
paths:
- "workflows/**"
- "plex-api-spec.yaml"
- ".github/workflows/arazzo-validation.yml"
pull_request:
branches: [main]
paths:
- "workflows/**"
- "plex-api-spec.yaml"
- ".github/workflows/arazzo-validation.yml"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: arazzo-validation-${{ github.ref }}
cancel-in-progress: true

jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout plex-api-spec
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false

- name: Clone Arbiter
run: |
git clone https://github.com/LukasParke/arbiter.git ../arbiter

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '24'

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: latest

- name: Build Arbiter
run: |
cd ../arbiter
pnpm install --frozen-lockfile
pnpm run build

- name: Set up Docker Compose
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2320ee5c88db75466b73 # v3.10.0

- name: Start Plex Media Server
run: |
docker compose up -d pms
for i in {1..60}; do
if curl -sf http://localhost:32400/identity > /dev/null 2>&1; then
echo "PMS is ready"
break
fi
echo "Waiting for PMS... ($i/60)"
sleep 5
done
env:
PLEX_CLAIM_TOKEN: ${{ secrets.PLEX_CLAIM_TOKEN }}

- name: Extract Plex Token
id: plex-token
run: |
# Wait for Plex to finish initialization and generate token
for i in {1..30}; do
TOKEN=$(docker exec plex-api-test cat /config/Library/Application\ Support/Plex\ Media\ Server/Preferences.xml 2>/dev/null | grep -oP 'PlexOnlineToken="\K[^"]+' || true)
if [ -n "$TOKEN" ]; then
echo "token=$TOKEN" >> $GITHUB_OUTPUT
echo "Found Plex token"
break
fi
echo "Waiting for Plex token... ($i/30)"
sleep 5
done

# Fallback: use a test token if available
if [ -z "${{ steps.plex-token.outputs.token }}" ]; then
echo "token=test-token" >> $GITHUB_OUTPUT
echo "Using fallback test token"
fi

- name: Start Arbiter Proxy with Validation
run: |
cd ../arbiter
node dist/src/cli.js start \
--target http://localhost:32400 \
--port 8080 \
--docs-port 9000 \
--db-path /tmp/arbiter.db \
--validate \
--spec "$GITHUB_WORKSPACE/plex-api-spec.yaml" \
--verbose > /tmp/arbiter.log 2>&1 &
PROXY_PID=$!
echo "PROXY_PID=$PROXY_PID" >> $GITHUB_ENV

for i in {1..30}; do
if curl -sf http://localhost:8080/identity > /dev/null 2>&1; then
echo "Arbiter proxy is ready (with validation enabled)"
break
fi
echo "Waiting for arbiter proxy... ($i/30)"
sleep 1
done

- name: Install Redocly Respect
run: |
npm install -g @redocly/cli@latest
npx @redocly/cli@latest --version

- name: Lint Arazzo Workflows
run: |
for wf in workflows/*.yaml; do
echo "=== Linting $(basename $wf) ==="
npx @redocly/cli@latest lint "$wf" || exit 1
done

- name: Run Arazzo Tests via Arbiter Proxy
run: |
FAILED=0
for wf in workflows/*.yaml; do
echo ""
echo "========================================"
echo "=== Running $(basename $wf) ==="
echo "========================================"
npx @redocly/cli@latest respect "$wf" \
--input plexToken=${{ steps.plex-token.outputs.token }} \
--input baseUrl=http://localhost:8080 \
|| FAILED=1
done
exit $FAILED

- name: Collect Arbiter Validation Log
if: always()
run: |
echo "=== Arbiter Proxy Validation Log ===" > /tmp/validation-summary.log
if [ -f /tmp/arbiter.log ]; then
grep -E "\[VALIDATE\]|Proxy error|Diff Report|Missing endpoints|Query param gaps" /tmp/arbiter.log >> /tmp/validation-summary.log || true
cat /tmp/validation-summary.log
fi

- name: Stop Arbiter Proxy
if: always()
run: |
kill $PROXY_PID 2>/dev/null || true
sleep 2

- name: Stop Plex Media Server
if: always()
run: docker compose down

- name: Upload Arbiter Log
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: arbiter-validation-log
path: /tmp/arbiter.log
retention-days: 7

- name: Upload Validation Summary
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: validation-summary
path: /tmp/validation-summary.log
retention-days: 7
120 changes: 120 additions & 0 deletions .github/workflows/discovery.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
name: Nightly Discovery Pipeline

on:
schedule:
- cron: '0 6 * * *'
workflow_dispatch:

permissions:
contents: read
actions: write
Comment on lines +8 to +10

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

The actions: write grant exceeds this errand's modest needs, sir.

Neither actions/checkout nor actions/upload-artifact@v4 calls upon the actions API scope — artifact uploads ride upon the runtime token rather than this permission. In the spirit of least privilege (and to silence zizmor's excessive-permissions reproach), I should counsel removing it. An explanatory comment would also appease the undocumented-permissions advisory.

🎩 A more frugal allotment
 permissions:
-  contents: read
-  actions: write
+  contents: read  # checkout only; artifact upload uses the runtime token
🧰 Tools
🪛 zizmor (1.25.2)

[error] 10-10: overly broad permissions (excessive-permissions): actions: write is overly broad at the workflow level

(excessive-permissions)


[warning] 10-10: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/discovery.yml around lines 8 - 10, Remove the unnecessary
actions: write permission from the workflow permissions block and replace it
with only the required minimal scope (keep contents: read); add a short
explanatory comment near the permissions block stating that actions/checkout and
actions/upload-artifact@v4 do not require the actions API scope and artifact
uploads use the runtime token, so actions: write was removed to follow
least-privilege and silence the excessive/undocumented-permissions checks;
reference the permissions block and the uses of actions/checkout and
actions/upload-artifact@v4 when making the change so future reviewers understand
why the write scope was omitted.


concurrency:
group: nightly-discovery
cancel-in-progress: true

jobs:
discover:
runs-on: ubuntu-latest
steps:
- name: Checkout plex-api-spec
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false

- name: Clone Arbiter
run: |
git clone https://github.com/LukasParke/arbiter.git ../arbiter
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '24'

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: latest

- name: Build Arbiter
run: |
cd ../arbiter
pnpm install --frozen-lockfile
pnpm run build

- name: Set up Docker Compose
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2320ee5c88db75466b73 # v3.10.0

- name: Start Plex Media Server
run: |
docker compose up -d pms
# Wait for PMS to be healthy
for i in {1..60}; do
if curl -sf http://localhost:32400/identity > /dev/null 2>&1; then
echo "PMS is ready"
break
fi
echo "Waiting for PMS... ($i/60)"
sleep 5
done
Comment on lines +48 to +59

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast when the readiness loops exhaust, sir.

Both polls merely finish their retries and then carry on. If PMS or Arbiter never becomes reachable, the workflow stumbles into later steps and fails with a misleading symptom instead of stopping at the actual fault.

🎩 A modest remedy
       - name: Start Plex Media Server
         run: |
           docker compose up -d pms
           # Wait for PMS to be healthy
           for i in {1..60}; do
             if curl -sf http://localhost:32400/identity > /dev/null 2>&1; then
               echo "PMS is ready"
               break
             fi
             echo "Waiting for PMS... ($i/60)"
             sleep 5
           done
+          curl -sf http://localhost:32400/identity > /dev/null 2>&1 || {
+            echo "PMS failed to become ready" >&2
+            docker compose logs pms || true
+            exit 1
+          }
@@
       - name: Start Arbiter Proxy
         run: |
           cd ../arbiter
           node dist/src/cli.js start \
             --target http://localhost:32400 \
             --port 8080 \
             --docs-port 9000 \
             --db-path /tmp/arbiter.db \
             --verbose &
           PROXY_PID=$!
           echo "PROXY_PID=$PROXY_PID" >> $GITHUB_ENV
           # Wait for proxy
           for i in {1..30}; do
             if curl -sf http://localhost:8080/identity > /dev/null 2>&1; then
               echo "Proxy is ready"
               break
             fi
             echo "Waiting for proxy... ($i/30)"
             sleep 1
           done
+          curl -sf http://localhost:8080/identity > /dev/null 2>&1 || {
+            echo "Arbiter failed to become ready" >&2
+            exit 1
+          }

Also applies to: 63-82

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/discovery.yml around lines 48 - 59, The readiness loops
for the "Start Plex Media Server" (and the similar Arbiter block) need to fail
fast when retries are exhausted: after the for-loop finishes without a
successful curl check, exit the step with a non-zero status. Modify the "Start
Plex Media Server" step's loop to detect when the loop completes (e.g., set a
flag or use an else/conditional after the loop) and call exit 1 with a clear
error message like "PMS not ready after X attempts"; apply the same change to
the Arbiter readiness loop so both steps stop the workflow immediately on
readiness failure.

env:
PLEX_CLAIM_TOKEN: ${{ secrets.PLEX_CLAIM_TOKEN }}

- name: Start Arbiter Proxy
run: |
cd ../arbiter
node dist/src/cli.js start \
--target http://localhost:32400 \
--port 8080 \
--docs-port 9000 \
--db-path /tmp/arbiter.db \
--verbose &
PROXY_PID=$!
echo "PROXY_PID=$PROXY_PID" >> $GITHUB_ENV
# Wait for proxy
for i in {1..30}; do
if curl -sf http://localhost:8080/identity > /dev/null 2>&1; then
echo "Proxy is ready"
break
fi
echo "Waiting for proxy... ($i/30)"
sleep 1
done

- name: Generate Traffic
run: |
cd ../arbiter
node dist/src/cli.js generate-traffic \
--target http://localhost:8080 \
--output /tmp/traffic.jsonl

- name: Stop Arbiter Proxy
run: |
kill $PROXY_PID || true
sleep 2

- name: Run Diff
run: |
cd ../arbiter
node dist/src/cli.js diff \
--spec ./plex-api-spec/plex-api-spec.yaml \
--traffic /tmp/traffic.jsonl \
--output /tmp/diff_report.json
Comment on lines +96 to +102

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

The diff step points at the wrong spec path, sir.

After cd ../arbiter, ./plex-api-spec/plex-api-spec.yaml resolves inside the Arbiter clone, not inside this checkout. The compose contract in docker-compose.yml shows the spec lives at this repository’s root as plex-api-spec.yaml, so the nightly diff can fail before it compares anything.

🎩 A steadier path
       - name: Run Diff
         run: |
           cd ../arbiter
           node dist/src/cli.js diff \
-            --spec ./plex-api-spec/plex-api-spec.yaml \
+            --spec "$GITHUB_WORKSPACE/plex-api-spec.yaml" \
             --traffic /tmp/traffic.jsonl \
             --output /tmp/diff_report.json
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/discovery.yml around lines 96 - 102, The Run Diff step
passes the wrong spec path to the Arbiter CLI; after running cd ../arbiter the
spec should be referenced one level up. Update the node dist/src/cli.js diff
invocation to use --spec ../plex-api-spec.yaml (instead of
./plex-api-spec/plex-api-spec.yaml) so the CLI loads the repo-root
plex-api-spec.yaml; keep the --traffic and --output flags as-is.


- name: Stop Plex Media Server
if: always()
run: docker compose down

- name: Upload diff report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: diff-report
path: /tmp/diff_report.json

- name: Upload traffic capture
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: traffic-capture
path: /tmp/traffic.jsonl
Comment thread
coderabbitai[bot] marked this conversation as resolved.
71 changes: 71 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Lint OpenAPI Spec

on:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pray add least-privilege token scope and disable checkout credential persistence.

Your workflow in .github/workflows/lint.yml defines on: but lacks a top-level permissions: block, and every actions/checkout@v4 step omits with: persist-credentials: false, so credentials are persisted by default.

Suggested least-privilege patch
 name: Lint OpenAPI Spec

 on:
@@
+permissions:
+  contents: read
+
 jobs:
   prettier:
@@
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
@@
   speakeasy:
@@
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
@@
   vacuum:
@@
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
on:
name: Lint OpenAPI Spec
on:
permissions:
contents: read
jobs:
prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
speakeasy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
vacuum:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 3-13: insufficient job-level concurrency limits (concurrency-limits): workflow is missing concurrency setting

(concurrency-limits)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/lint.yml at line 3, Add a top-level permissions: block to
the workflow to enforce least-privilege (e.g., set default token scopes to only
what linting needs) and update every actions/checkout@v4 step to include with:
persist-credentials: false so the GITHUB_TOKEN is not persisted to the
workspace; locate the workflow root (the on: block) and insert the permissions:
block, and locate each actions/checkout@v4 usage to add the persist-credentials:
false setting.

push:
branches: [main]
paths:
- "plex-api-spec.yaml"
- ".github/workflows/lint.yml"
pull_request:
branches: [main]
paths:
- "plex-api-spec.yaml"
- ".github/workflows/lint.yml"
Comment on lines +3 to +13

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

A modest concurrency guard would save wasted runner time.

Adding workflow concurrency here prevents older in-flight runs from consuming CI minutes once superseded by newer commits.

Suggested concurrency block
 on:
@@
+concurrency:
+  group: lint-openapi-${{ github.ref }}
+  cancel-in-progress: true
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
on:
push:
branches: [main]
paths:
- "plex-api-spec.yaml"
- ".github/workflows/lint.yml"
pull_request:
branches: [main]
paths:
- "plex-api-spec.yaml"
- ".github/workflows/lint.yml"
on:
push:
branches: [main]
paths:
- "plex-api-spec.yaml"
- ".github/workflows/lint.yml"
pull_request:
branches: [main]
paths:
- "plex-api-spec.yaml"
- ".github/workflows/lint.yml"
concurrency:
group: lint-openapi-${{ github.ref }}
cancel-in-progress: true
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 3-13: insufficient job-level concurrency limits (concurrency-limits): workflow is missing concurrency setting

(concurrency-limits)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/lint.yml around lines 3 - 13, Add a concurrency block to
the workflow to cancel superseded runs: add a top-level "concurrency" key with a
stable group identifier (e.g., using github.ref or github.workflow + github.ref)
and set "cancel-in-progress: true" so newer commits cancel older in-flight runs;
update the existing workflow definition in the lint.yml workflow to include this
concurrency configuration.


jobs:
prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
Comment on lines +19 to +20

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/workflows/lint.yml"
echo "== File: $FILE =="
wc -l "$FILE"
sed -n '1,200p' "$FILE" | nl -ba

Repository: LukasParke/plex-api-spec

Length of output: 198


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/workflows/lint.yml"
echo "== File: $FILE =="
wc -l "$FILE"
sed -n '1,200p' "$FILE" | nl -ba

Repository: LukasParke/plex-api-spec

Length of output: 198


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/workflows/lint.yml"
echo "== File: $FILE =="
wc -l "$FILE"
echo "== Contents (with line numbers) =="
cat -n "$FILE" | sed -n '1,120p'

Repository: LukasParke/plex-api-spec

Length of output: 1583


🌐 Web query:

actions/checkout v4 persist-credentials default value

💡 Result:

The default value of actions/checkout v4 input persist-credentials is true (i.e., credentials/token or SSH key are configured in the local git config by default). [1][2] The README also states the behavior: “The auth token is persisted in the local git config… Set persist-credentials: false to opt-out.” [2]

Citations:


🌐 Web query:

GitHub Actions default GITHUB_TOKEN permissions when no permissions block is set

💡 Result:

By default, GitHub Actions grants the GITHUB_TOKEN the repository/organization/enterprise “Workflow permissions” defaults set for the repo; you only override those defaults when you add a permissions: block to the workflow/job. GitHub’s docs also note that if a workflow uses the permissions key, any scopes you do not explicitly set are denied (with an exception for metadata, which always gets read access). [1][2] What this means when you do NOT set any permissions block: 1) The GITHUB_TOKEN permissions start as the default “Workflow permissions” configuration for your repository (which may be restricted or permissive depending on org/enterprise settings). [1] 2) For new repositories in a personal account, GitHub states the default is “only has read access for the contents and packages scopes.” [1] 3) If you set a permissions key at all (workflow/job), then unspecified permissions become “no access”, except metadata which is always read. [3] So, for the specific case “no permissions block is set”, the practical answer is: you get whatever the repo’s configured default workflow permissions are (often restricted to read-only contents and packages for personal repos); you don’t get automatic broad read/write just because the workflow omitted the block. [1][4]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
ls -la
find . -maxdepth 2 -type f \( -name "package.json" -o -name "package-lock.json" -o -name "pnpm-lock.yaml" -o -name "yarn.lock" \) -print

Repository: LukasParke/plex-api-spec

Length of output: 1167


🌐 Web query:

actions/checkout v4 persist-credentials default value

💡 Result:

The default value for the persist-credentials input in actions/checkout v4 is true [1][2][3]. When set to true (the default), the action configures the authentication token or SSH key with the local git configuration [1][3]. This allows your subsequent workflow steps to perform authenticated git commands [3][4]. The token is automatically removed during the post-job cleanup process [3][5]. To prevent the action from persisting these credentials, you can explicitly set persist-credentials to false [3][4].

Citations:


🌐 Web query:

GitHub Actions default GITHUB_TOKEN permissions when no permissions block is set

💡 Result:

When no permissions block is defined in a GitHub Actions workflow, the GITHUB_TOKEN permissions default to the settings configured at the repository, organization, or enterprise level [1][2]. Repositories newly created on GitHub today typically have a restricted default setting where the GITHUB_TOKEN is granted read-only access to the contents and packages scopes [1][3]. However, this behavior depends on the specific settings chosen by repository or organization administrators [1][2]. Administrators can configure the default GITHUB_TOKEN permissions via the repository or organization settings under Actions > General > Workflow permissions [1][4]. There are generally two configurable options: 1. Permissive: Read and write access for all scopes [1][5]. 2. Restricted: Read-only access for repository contents and packages [1][5]. If a repository belongs to an organization, it will inherit the organization's default permission setting unless a more restrictive setting is enforced [1][2]. Furthermore, regardless of these default settings, the GITHUB_TOKEN is always downgraded to read-only access when a workflow is triggered by a pull request from a forked repository (unless the specific repository setting to send write tokens to workflows from pull requests is enabled) [5][2]. It is considered a security best practice to explicitly define the required permissions in the workflow file using the permissions key to follow the principle of least privilege [6][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "== Top-level files =="
ls -la
echo "== Lockfiles / package manifests (max depth 2) =="
find . -maxdepth 2 -type f \( -name "package.json" -o -name "package-lock.json" -o -name "pnpm-lock.yaml" -o -name "yarn.lock" \) -print

Repository: LukasParke/plex-api-spec

Length of output: 1239


Pin GitHub Actions to full commit SHAs (and harden mutable downloads) — most certainly, sir.

In .github/workflows/lint.yml, the workflow uses tag-based action references (actions/checkout@v4, actions/setup-node@v4, speakeasy-api/sdk-generation-action@v15) at lines 19-20, 29-30, and 38—an avoidable supply-chain risk under strict enforcement. Additionally, it pulls releases/latest for Vacuum and runs floating npm installs.

  • Pin uses: for:
    • actions/checkout@v4 (lines 19, 29, 38),
    • actions/setup-node@v4 (line 20),
    • speakeasy-api/sdk-generation-action@v15 (line 30),
      to full commit SHAs; for each actions/checkout step also set persist-credentials: false.
  • Replace npm install prettier prettier-plugin-openapi with deterministic installs (prefer npm ci using package-lock.json, and pin npm package versions instead of leaving them floating).
  • Stop downloading Vacuum from releases/latest without integrity checks—use a pinned version and verify a checksum (or use an official checksum mechanism).
  • Add an explicit permissions: block for least privilege (don’t rely on repository defaults), and consider adding concurrency: to prevent overlapping lint runs.
Suggested hardening patch
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@<FULL_COMMIT_SHA_FOR_V4>
...
-      - uses: actions/setup-node@v4
+      - uses: actions/setup-node@<FULL_COMMIT_SHA_FOR_V4>
...
-      - uses: speakeasy-api/sdk-generation-action@v15
+      - uses: speakeasy-api/sdk-generation-action@<FULL_COMMIT_SHA_FOR_V15>
...
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@<FULL_COMMIT_SHA_FOR_V4>
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 19-19: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 19-19: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 20-20: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/lint.yml around lines 19 - 20, The workflow uses mutable
action tags and floating installs; update each `uses:` reference for
`actions/checkout@v4`, `actions/setup-node@v4`, and
`speakeasy-api/sdk-generation-action@v15` to pinned commit SHAs and add
`persist-credentials: false` to each `actions/checkout` step; replace the `npm
install prettier prettier-plugin-openapi` step with deterministic installs (use
`npm ci` and pin package versions in package-lock.json or package.json); stop
downloading Vacuum from `releases/latest`—pin a specific Vacuum release and add
checksum verification for the downloaded artifact; and add an explicit
`permissions:` block with least-privilege tokens and a `concurrency:` key to the
workflow to prevent overlapping lint runs (look for the job definitions and
steps referencing `uses:`, the npm install step, the Vacuum download step, and
job-level keys to apply these changes).

with:
node-version: '20'
- run: npm install prettier prettier-plugin-openapi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show lint workflow around the referenced line
echo "=== .github/workflows/lint.yml (lines 1-120) ==="
nl -ba .github/workflows/lint.yml | sed -n '1,120p'

# Confirm npm install usage in the workflow(s)
echo
echo "=== Search for 'npm install' in .github/workflows ==="
rg -n "npm (install|ci)" .github/workflows || true

# Check for package-lock presence (needed for npm ci)
echo
echo "=== Check for package-lock.json ==="
ls -la package-lock.json 2>/dev/null || true

Repository: LukasParke/plex-api-spec

Length of output: 178


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== .github/workflows/lint.yml (lines 1-200) ==="
sed -n '1,200p' .github/workflows/lint.yml

echo
echo "=== Search for 'npm install' / 'npm ci' in .github/workflows ==="
rg -n "npm (install|ci)" .github/workflows || true

echo
echo "=== Check for package-lock.json / npm-shrinkwrap.json ==="
ls -la package-lock.json npm-shrinkwrap.json 2>/dev/null || true

echo
echo "=== Check if repo uses pnpm/yarn instead of npm ==="
ls -la yarn.lock pnpm-lock.yaml 2>/dev/null || true

Repository: LukasParke/plex-api-spec

Length of output: 1560


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Inspect package.json ==="
ls -la package.json || true
cat package.json

echo
echo "=== Check whether prettier-plugin-openapi is in package-lock.json ==="
rg -n "\"prettier-plugin-openapi\"" package-lock.json || true

echo
echo "=== Check whether prettier is in package-lock.json ==="
rg -n "\"prettier\"" package-lock.json | head -n 20 || true

Repository: LukasParke/plex-api-spec

Length of output: 535


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== package-lock.json header ==="
head -n 40 package-lock.json

Repository: LukasParke/plex-api-spec

Length of output: 1441


🌐 Web query:

npm ci --include=dev option

💡 Result:

For npm ci, use the --include option to select which dependency types from the lockfile are physically installed. 1) Meaning of --include=dev - --include “allows for defining which types of dependencies to install” and is the inverse of --omit [1]. - When you pass --include=dev, dependency type dev will be included (i.e., physically installed), rather than omitted [1]. 2) Interaction with the default dev behavior - By default, npm sets the default include behavior based on NODE_ENV: it is 'dev' when NODE_ENV is not set to 'production', otherwise empty [1]. - So npm ci normally installs dev dependencies unless NODE_ENV=production; --include=dev is how you explicitly force dev dependencies to be installed even if other omit logic would exclude them [1]. 3) If dev is listed in both --include and --omit - If a package type appears in both --include and --omit, then it will be included [1]. Example - Install a clean project and ensure devDependencies are installed: npm ci --include=dev Sources: npm npm-ci command reference for the --include/--omit options and default NODE_ENV behavior [1].

Citations:


Avoid a floating npm install in CI; use the lockfile via npm ci.
Line 23 installs prettier/prettier-plugin-openapi at runtime from npm (latest), bypassing package-lock.json, which makes lint results drift. Pray switch to a lockfile-based install—both packages already live in devDependencies.

Suggested deterministic install
-      - run: npm install prettier prettier-plugin-openapi
+      - run: npm ci --include=dev
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- run: npm install prettier prettier-plugin-openapi
- run: npm ci --include=dev
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/lint.yml at line 23, Replace the floating runtime install
"npm install prettier prettier-plugin-openapi" in the GitHub Actions step with a
lockfile-driven install (use "npm ci") so CI uses package-lock.json and
deterministic versions; update the step that currently contains the run: npm
install prettier prettier-plugin-openapi command to run npm ci (and keep the
existing devDependencies for prettier and prettier-plugin-openapi).

- run: npx prettier --check plex-api-spec.yaml

speakeasy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Speakeasy
run: |
curl -fsSL https://raw.githubusercontent.com/speakeasy-api/speakeasy/main/install.sh | sh
speakeasy --version
- run: speakeasy lint openapi -s plex-api-spec.yaml

vacuum:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4

- name: Install Vacuum
run: |
gh release download --repo daveshanley/vacuum --pattern '*linux_x86_64.tar.gz'
tar -xzf vacuum_*_linux_x86_64.tar.gz
sudo mv vacuum /usr/local/bin/
Comment on lines +43 to +47

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the workflow content around the referenced lines
FILE=".github/workflows/lint.yml"
echo "== File: $FILE =="
nl -ba "$FILE" | sed -n '1,120p'
nl -ba "$FILE" | sed -n '30,70p'

# Extract just the Install Vacuum step for precise comparison
echo "== Install Vacuum step (context) =="
rg -n "Install Vacuum|vacuum_linux_amd64|releases/latest" "$FILE" -n || true

Repository: LukasParke/plex-api-spec

Length of output: 168


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/workflows/lint.yml"
echo "== File: $FILE =="
awk '{printf "%4d\t%s\n", NR, $0}' "$FILE" | sed -n '1,120p'
echo "== Around lines 39-42 =="
awk '{printf "%4d\t%s\n", NR, $0}' "$FILE" | sed -n '30,70p'

echo "== Matches for vacuum install =="
rg -n "Vacuum|vacuum_linux_amd64|releases/latest|vacuum/releases" "$FILE" || true

Repository: LukasParke/plex-api-spec

Length of output: 2209


🌐 Web query:

Vacuum (daveshanley) GitHub releases vacuum_linux_amd64 checksums.txt sha256

💡 Result:

Vacuum (daveshanley) GitHub release checksums location/lookup: For a given version X.Y.Z, the installer script fetches the checksum file from: https://github.com/daveshanley/vacuum/releases/download/v$1/checksums.txt [1] It then selects the expected SHA-256 by grepping for the asset name (which in the release assets is of the form vacuum__linux_x86_64.tar.gz; the script maps machine types so amd64/x86_64 become x86_64) [1]. Example for the latest releases around today (2026-06-03): - v0.27.2 release exists on GitHub [2] and includes checksums.txt as a release asset [2]. - v0.27.1 release exists on GitHub [3] and includes checksums.txt as a release asset [3]. Important: I could not retrieve the actual checksum line value for “vacuum_linux_amd64 checksums.txt sha256” from the GitHub release asset contents within the provided search results. However, the authoritative way to get the exact SHA-256 is to download the release’s checksums.txt and read the line for the linux amd64 artifact (which corresponds to linux_x86_64 in the installer’s naming) [1]. How to get the sha256 yourself (matches the project’s installer logic): 1) Pick the version tag (e.g., v0.27.2) [2]. 2) Download checksums.txt: curl -sSL "https://github.com/daveshanley/vacuum/releases/download/v0.27.2/checksums.txt" -o checksums.txt 3) Extract the sha256 for the linux amd64 artifact name used by the installer (linux_x86_64): grep "vacuum_0.27.2_linux_x86_64.tar.gz" checksums.txt | awk '{print $1}' If you tell me which exact version tag you mean (e.g., v0.27.2) I can re-run a targeted lookup specifically for that version’s linux amd64 checksum entry.

Citations:


🌐 Web query:

raw.githubusercontent.com daveshanley vacuum main bin install.sh grep vacuum_linux_x86_64 sha256 checksums.txt

💡 Result:

The script located at https://raw.githubusercontent.com/daveshanley/vacuum/main/bin/install.sh is an automated installer for the vacuum OpenAPI linter [1][2]. It is designed to detect the user's operating system and architecture, download the appropriate release binary from GitHub, verify it against a checksums.txt file, and install it into a local directory [1]. Regarding your query about finding the SHA256 checksum for vacuum_linux_x86_64, the installation script itself does not contain hardcoded checksums. Instead, it dynamically downloads a checksums.txt file from the specific release version's assets on GitHub [1][3]. To obtain the current checksum for a specific version, you should: 1. Visit the official GitHub releases page for daveshanley/vacuum: https://github.com/daveshanley/vacuum/releases [4]. 2. Select the desired version (e.g., v0.27.2) [5][6]. 3. Locate and download the checksums.txt file under the "Assets" section of that release [7][8][9]. 4. Grep for the specific binary to see its corresponding hash, for example: grep vacuum_linux_x86_64 checksums.txt The vacuum project is an OpenAPI linter written in Go [4][5]. The primary installation method recommended by the author involves using the install script via curl (curl -fsSL https://quobix.com/scripts/install_vacuum.sh | sh) or using tools like Homebrew [10][4].

Citations:


Pin and checksum-verify Vacuum; avoid releases/latest
In .github/workflows/lint.yml the install step (line 41) downloads from releases/latest and immediately pipes into tar with no integrity check—this is a supply-chain risk. Pin a specific Vacuum version and verify the downloaded artifact against that version’s checksums.txt before extracting/moving the binary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/lint.yml around lines 39 - 42, The "Install Vacuum"
workflow step currently streams from releases/latest into tar (curl ... | tar
-xz) with no integrity check; change it to pin a specific Vacuum version (set a
VERSION variable instead of using releases/latest), download the matching
vacuum_linux_amd64.tar.gz and its checksums.txt for that VERSION, compute and
verify the artifact's checksum (e.g., sha256) matches the entry in checksums.txt
before extracting, and only after successful verification extract and move the
binary (replacing the direct pipe to tar and the unconditional sudo mv vacuum
/usr/local/bin/).

vacuum version
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Lint with Vacuum
run: |
vacuum lint plex-api-spec.yaml \
--details \
--pipeline-output \
--no-banner \
--min-score 25

- name: Generate Vacuum Report
if: always()
run: |
vacuum report plex-api-spec.yaml report-prefix --no-pretty

- name: Upload Vacuum Report
if: always()
uses: actions/upload-artifact@v4
with:
name: vacuum-report
path: report-prefix*.json
retention-days: 30
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# Finder (MacOS) folder config
.DS_Store
report-prefix*.json
# Plex test data generated by docker compose
test-data/
3 changes: 2 additions & 1 deletion .speakeasy/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ defaultRuleset: PathParamRuleset
rulesets:
PathParamRuleset:
rulesets:
- speakeasy-generation # Use the speakeasy-generation ruleset as a base
- speakeasy-generation
- speakeasy-recommended
Loading
Loading