From e1cb13883b063d61359759bb4e8b97dd5e3121af Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Fri, 17 Apr 2026 02:27:37 +1000 Subject: [PATCH 01/28] Use git commands for auto-merge instead of API --- .gitea/workflows/validate-pr.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/validate-pr.yml b/.gitea/workflows/validate-pr.yml index ff36ea0..1186b38 100644 --- a/.gitea/workflows/validate-pr.yml +++ b/.gitea/workflows/validate-pr.yml @@ -31,8 +31,11 @@ jobs: if: success() run: | echo "✅ Validation passed - Auto-merging PR" - curl -X POST \ - -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - -H "Content-Type: application/json" \ - "http://synbox.ruv.wtf:8418/api/v1/repos/litruv/Plugin-Directory/pulls/${{ github.event.pull_request.number }}/merge" \ - -d '{"Do":"squash","MergeTitleField":"${{ github.event.pull_request.title }}","MergeMessageField":"Auto-merged after validation","delete_branch_after_merge":true}' + git config user.name "GitHub Actions" + git config user.email "actions@github.com" + git fetch origin ${{ github.event.pull_request.head.ref }} + git checkout main + git merge --squash origin/${{ github.event.pull_request.head.ref }} + git commit -m "${{ github.event.pull_request.title }}" + git push origin main + git push origin --delete ${{ github.event.pull_request.head.ref }} From 0fe5f7077ecd50da6c1f515220f4a9e68d0028b6 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 16 Apr 2026 16:28:56 +0000 Subject: [PATCH 02/28] Add Example Plugin 2 --- plugins/example-plugin-2.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 plugins/example-plugin-2.json diff --git a/plugins/example-plugin-2.json b/plugins/example-plugin-2.json new file mode 100644 index 0000000..ec79b81 --- /dev/null +++ b/plugins/example-plugin-2.json @@ -0,0 +1,12 @@ +{ + "id": "example-plugin-2", + "name": "Example Plugin 2", + "version": "1.0.0", + "description": "A second example plugin demonstrating additional capabilities", + "author": "litruv", + "repository": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example2.git", + "downloadUrl": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example2/archive/main.zip", + "homepage": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example2", + "tags": ["example", "demo", "filtering", "validation"], + "addedDate": "2026-04-17T00:00:00.000Z" +} \ No newline at end of file From 18fb15d82dddc5f577d9a1e67c28163ac9cd5214 Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Fri, 17 Apr 2026 02:31:13 +1000 Subject: [PATCH 03/28] Simplify index update script --- .gitea/scripts/update-index.js | 28 ++++++++++++++++++++++++++++ .gitea/workflows/update-index.yml | 28 ++++++---------------------- plugins/index.json | 5 +++-- 3 files changed, 37 insertions(+), 24 deletions(-) create mode 100644 .gitea/scripts/update-index.js diff --git a/.gitea/scripts/update-index.js b/.gitea/scripts/update-index.js new file mode 100644 index 0000000..23e5dcb --- /dev/null +++ b/.gitea/scripts/update-index.js @@ -0,0 +1,28 @@ +import { readdir, readFile, writeFile } from 'fs/promises'; +import { join } from 'path'; + +const pluginsDir = './plugins'; + +async function updateIndex() { + const files = await readdir(pluginsDir); + const pluginIds = files + .filter(f => f.endsWith('.json') && f !== 'index.json') + .map(f => f.replace('.json', '')) + .sort(); + + const index = { + version: '1.0.0', + updatedAt: new Date().toISOString(), + plugins: pluginIds + }; + + await writeFile( + join(pluginsDir, 'index.json'), + JSON.stringify(index, null, 2) + '\n' + ); + + console.log(`✅ Updated index.json with ${pluginIds.length} plugins`); + console.log('Plugins:', pluginIds.join(', ')); +} + +updateIndex().catch(console.error); diff --git a/.gitea/workflows/update-index.yml b/.gitea/workflows/update-index.yml index 0e1ed0e..f13f6ca 100644 --- a/.gitea/workflows/update-index.yml +++ b/.gitea/workflows/update-index.yml @@ -13,29 +13,13 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + - name: Generate index.json - run: | - cd plugins - echo '{' > index.json - echo ' "version": "1.0.0",' >> index.json - echo ' "updatedAt": "'$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")'",' >> index.json - echo ' "plugins": [' >> index.json - - first=true - for file in *.json; do - if [ "$file" != "index.json" ]; then - plugin_id="${file%.json}" - if [ "$first" = true ]; then - echo " \"$plugin_id\"" >> index.json - first=false - else - echo ", \"$plugin_id\"" >> index.json - fi - fi - done | sed '$ s/,$//' - - echo ' ]' >> index.json - echo '}' >> index.json + run: node .gitea/scripts/update-index.js - name: Commit index.json run: | diff --git a/plugins/index.json b/plugins/index.json index d5907a4..1aeb13b 100644 --- a/plugins/index.json +++ b/plugins/index.json @@ -1,7 +1,8 @@ { "version": "1.0.0", - "updatedAt": "2026-04-17T00:00:00.000Z", + "updatedAt": "2026-04-16T16:31:13.860Z", "plugins": [ - "example-plugin" + "example-plugin", + "example-plugin-2" ] } From 3ebdc6b5613c254df507e253e9594b26e3702cd6 Mon Sep 17 00:00:00 2001 From: Gitea Actions Date: Thu, 16 Apr 2026 16:31:23 +0000 Subject: [PATCH 04/28] Auto-update plugin index [skip ci] --- plugins/index.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/index.json b/plugins/index.json index 1aeb13b..1800f63 100644 --- a/plugins/index.json +++ b/plugins/index.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "updatedAt": "2026-04-16T16:31:13.860Z", + "updatedAt": "2026-04-16T16:31:22.910Z", "plugins": [ "example-plugin", "example-plugin-2" From edea39e558eefb6804663ff714d7ddff533312cb Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Fri, 17 Apr 2026 02:47:56 +1000 Subject: [PATCH 05/28] Add thumbnail support and validation --- .gitea/scripts/validate-pr.js | 103 +++++++++++++++++++++++++++++++++- plugins/example-plugin.json | 1 + 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/.gitea/scripts/validate-pr.js b/.gitea/scripts/validate-pr.js index 55038f5..b32e044 100644 --- a/.gitea/scripts/validate-pr.js +++ b/.gitea/scripts/validate-pr.js @@ -10,12 +10,93 @@ * 4. Plugin ID matches filename * 5. Author matches PR creator (for new plugins) * 6. Can only remove own plugins + * 7. Thumbnail requirements: png/gif/jpg, max 512x512, max 2MB */ import { readFile, access } from 'fs/promises'; import { join } from 'path'; +import https from 'https'; +import http from 'http'; const REQUIRED_FIELDS = ['id', 'name', 'version', 'description', 'author', 'repository']; +const MAX_THUMBNAIL_SIZE = 2 * 1024 * 1024; // 2MB +const MAX_THUMBNAIL_DIMENSIONS = 512; +const ALLOWED_IMAGE_FORMATS = ['png', 'gif', 'jpg', 'jpeg']; + +async function validateThumbnail(url) { + return new Promise((resolve, reject) => { + const protocol = url.startsWith('https') ? https : http; + + protocol.get(url, (res) => { + if (res.statusCode !== 200) { + return reject(new Error(`Thumbnail URL returned ${res.statusCode}`)); + } + + const chunks = []; + let size = 0; + + res.on('data', (chunk) => { + chunks.push(chunk); + size += chunk.length; + + if (size > MAX_THUMBNAIL_SIZE) { + res.destroy(); + reject(new Error(`Thumbnail exceeds 2MB limit (${(size / 1024 / 1024).toFixed(2)}MB)`)); + } + }); + + res.on('end', () => { + const buffer = Buffer.concat(chunks); + + // Check image format by magic bytes + const isPNG = buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4E && buffer[3] === 0x47; + const isJPG = buffer[0] === 0xFF && buffer[1] === 0xD8 && buffer[2] === 0xFF; + const isGIF = buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46; + + if (!isPNG && !isJPG && !isGIF) { + return reject(new Error('Thumbnail must be PNG, JPG, or GIF format')); + } + + // Check dimensions (simplified check - reads PNG/JPG headers) + let width, height; + + if (isPNG) { + width = buffer.readUInt32BE(16); + height = buffer.readUInt32BE(20); + } else if (isJPG) { + // Simplified JPEG dimension reading + let offset = 2; + while (offset < buffer.length) { + if (buffer[offset] !== 0xFF) break; + offset++; + const marker = buffer[offset]; + offset++; + + if (marker === 0xC0 || marker === 0xC2) { + height = buffer.readUInt16BE(offset + 3); + width = buffer.readUInt16BE(offset + 5); + break; + } + + const segmentLength = buffer.readUInt16BE(offset); + offset += segmentLength; + } + } else if (isGIF) { + width = buffer.readUInt16LE(6); + height = buffer.readUInt16LE(8); + } + + if (width > MAX_THUMBNAIL_DIMENSIONS || height > MAX_THUMBNAIL_DIMENSIONS) { + return reject(new Error(`Thumbnail dimensions ${width}x${height} exceed ${MAX_THUMBNAIL_DIMENSIONS}x${MAX_THUMBNAIL_DIMENSIONS}`)); + } + + resolve({ width, height, size, format: isPNG ? 'PNG' : isJPG ? 'JPG' : 'GIF' }); + }); + + res.on('error', reject); + }).on('error', reject); + }); +} const prAuthor = process.argv[2]; const changedFiles = process.argv[3]?.split('\n').filter(f => f.trim()) || []; @@ -23,7 +104,24 @@ const changedFiles = process.argv[3]?.split('\n').filter(f => f.trim()) || []; if (!prAuthor) { console.error('❌ Error: PR author not provided'); process.exit(1); -} +}// Check thumbnail if provided (optional) + if (plugin.thumbnail) { + try { + new URL(plugin.thumbnail); + + // Validate thumbnail meets requirements + try { + const thumbInfo = await validateThumbnail(plugin.thumbnail); + console.log(` ✓ Thumbnail: ${thumbInfo.format} ${thumbInfo.width}x${thumbInfo.height} (${(thumbInfo.size / 1024).toFixed(1)}KB)`); + } catch (thumbErr) { + errors.push(`❌ ${file}: Thumbnail validation failed: ${thumbErr.message}`); + } + } catch { + errors.push(`❌ ${file}: Invalid thumbnail URL: ${plugin.thumbnail}`); + } + } + + console.log(`\n🔍 Validating PR from: ${prAuthor}`); console.log(`📝 Changed files: ${changedFiles.length}`); @@ -115,8 +213,7 @@ for (const file of changedFiles) { } else if (isRemoved) { // For removed files, we can't validate ownership easily in CI - // Just track that it was removed - removed.push(pluginId); + // Just track that it was removed console.log(' 7. Thumbnail (optional): PNG/JPG/GIF, max 512x512, max 2MB'); removed.push(pluginId); console.log(`🗑️ Removed: ${pluginId}`); } } diff --git a/plugins/example-plugin.json b/plugins/example-plugin.json index ee403f5..0fd5867 100644 --- a/plugins/example-plugin.json +++ b/plugins/example-plugin.json @@ -5,6 +5,7 @@ "description": "An example plugin demonstrating the plugin system capabilities", "author": "litruv", "repository": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example.git", + "thumbnail": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example/raw/branch/main/thumbnail.png", "downloadUrl": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example/archive/main.zip", "homepage": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example", "tags": ["example", "demo"], From b9ad7c89a8c02e0c8c8d907d5715c9605af1c3ab Mon Sep 17 00:00:00 2001 From: Gitea Actions Date: Thu, 16 Apr 2026 16:48:14 +0000 Subject: [PATCH 06/28] Auto-update plugin index [skip ci] --- plugins/index.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/index.json b/plugins/index.json index 1800f63..36c211b 100644 --- a/plugins/index.json +++ b/plugins/index.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "updatedAt": "2026-04-16T16:31:22.910Z", + "updatedAt": "2026-04-16T16:48:14.146Z", "plugins": [ "example-plugin", "example-plugin-2" From d6f57ddf10352e712dc046c68ccc9da98bea60d3 Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Fri, 17 Apr 2026 02:50:49 +1000 Subject: [PATCH 07/28] Security fix: prevent validation script injection in PRs --- .gitea/workflows/validate-pr.yml | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/validate-pr.yml b/.gitea/workflows/validate-pr.yml index 1186b38..a284687 100644 --- a/.gitea/workflows/validate-pr.yml +++ b/.gitea/workflows/validate-pr.yml @@ -2,15 +2,21 @@ name: Validate Plugin PR on: pull_request: branches: [ main ] - paths: - - 'plugins/**' jobs: validate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout base branch (main) for validation scripts + uses: actions/checkout@v3 with: + ref: main + path: base + + - name: Checkout PR branch for plugin files + uses: actions/checkout@v3 + with: + path: pr fetch-depth: 0 - name: Setup Node.js @@ -21,11 +27,26 @@ jobs: - name: Get changed files id: changed-files run: | + cd pr echo "files=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep '^plugins/' || echo '')" >> $GITHUB_OUTPUT - - name: Validate PR + - name: Block attempts to modify infrastructure run: | - node .gitea/scripts/validate-pr.js "${{ github.event.pull_request.user.login }}" "${{ steps.changed-files.outputs.files }}" + cd pr + INFRA_CHANGES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E '^\.gitea/' || echo '') + if [ -n "$INFRA_CHANGES" ]; then + echo "❌ ERROR: PRs cannot modify .gitea/ directory files" + echo "Changed infrastructure files:" + echo "$INFRA_CHANGES" + echo "" + echo "Only plugins/ directory changes are allowed." + exit 1 + fi + + - name: Validate PR (using trusted validation script from main) + run: | + node base/.gitea/scripts/validate-pr.js "${{ github.event.pull_request.user.login }}" "${{ steps.changed-files.outputs.files }}" + working-directory: pr - name: Auto-merge PR if: success() From 82ae0a05b0a04393f639678466b461b81f010672 Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Fri, 17 Apr 2026 02:51:23 +1000 Subject: [PATCH 08/28] Add comprehensive validation rules documentation --- VALIDATION_RULES.md | 168 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 VALIDATION_RULES.md diff --git a/VALIDATION_RULES.md b/VALIDATION_RULES.md new file mode 100644 index 0000000..915fc6e --- /dev/null +++ b/VALIDATION_RULES.md @@ -0,0 +1,168 @@ +# Plugin Validation Rules + +All plugin submissions are automatically validated before being merged. PRs must pass all validation checks to be auto-merged. + +## Security + +**The validation script runs from the main branch, not from your PR branch.** This prevents malicious PRs from modifying the validation logic. PRs that attempt to modify `.gitea/` directory files will be automatically rejected. + +## File Requirements + +### 1. Location +- ✅ Only modify files in `plugins/` directory +- ❌ Cannot modify files outside `plugins/` +- ❌ Cannot modify `.gitea/` infrastructure files +- ⚠️ `plugins/index.json` is auto-generated - don't manually edit it + +### 2. File Type +- ✅ Only `.json` files are allowed in `plugins/` directory +- ❌ No other file types (images, scripts, etc. must be in plugin repo) + +## Plugin JSON Schema + +### Required Fields +Every plugin JSON must have these fields: + +```json +{ + "id": "string", + "name": "string", + "version": "string", + "description": "string", + "author": "string", + "repository": "string" +} +``` + +### Field Validation Rules + +#### `id` (required) +- Must match the filename +- Example: `example-plugin.json` → `"id": "example-plugin"` +- Lowercase, hyphens allowed, no spaces + +#### `name` (required) +- Display name for the plugin +- Human-readable, any format + +#### `version` (required) +- Must follow semantic versioning: `x.y.z` +- Example: `"1.0.0"`, `"2.1.3"` +- ❌ Invalid: `"v1.0"`, `"1.0"`, `"latest"` + +#### `description` (required) +- Short description of what the plugin does +- Will be shown in the plugin browser + +#### `author` (required) +- **MUST MATCH YOUR GITEA USERNAME** +- This enforces ownership - you can only submit/remove plugins authored by you +- Case-sensitive + +#### `repository` (required) +- Git repository URL where the plugin code lives +- Must be a valid URL +- Example: `"http://synbox.ruv.wtf:8418/username/Plugin-Name.git"` + +#### `thumbnail` (optional) +- URL to plugin thumbnail image +- **Requirements:** + - Format: PNG, JPG, or GIF only + - Max dimensions: 512×512 pixels + - Max file size: 2MB + - Must be accessible via HTTP/HTTPS +- Example: `"http://synbox.ruv.wtf:8418/username/Plugin-Name/raw/branch/main/thumbnail.png"` +- Validation checks actual image format (magic bytes), dimensions, and size + +### Optional Fields +You can include any additional fields for metadata: +- `homepage` - Project homepage URL +- `downloadUrl` - Direct download link +- `tags` - Array of tags +- `addedDate` - ISO timestamp + +## Ownership Rules + +### Adding Plugins +- The `author` field must match your Gitea username +- This is checked automatically + +### Removing Plugins +- You can only remove plugins where `author` matches your username +- Attempting to remove someone else's plugin will fail validation + +## Validation Process + +1. **File check**: Ensures only `plugins/*.json` files are modified +2. **Infrastructure check**: Blocks any `.gitea/` modifications +3. **JSON parsing**: Validates JSON syntax +4. **Schema validation**: Checks all required fields exist +5. **Field validation**: Validates each field's format and rules +6. **Thumbnail validation** (if provided): Downloads and validates image +7. **Author matching**: Ensures `author` field matches PR creator +8. **Auto-merge**: If all checks pass, PR is automatically merged + +## Testing Locally + +You can test your plugin JSON before submitting: + +```bash +cd Plugin-Directory +node .gitea/scripts/validate-pr.js "your-username" "plugins/your-plugin.json" +``` + +## Common Errors + +### ❌ Author mismatch +``` +❌ plugins/example-plugin.json: Author "someone" must match PR creator "you" +``` +**Fix**: Change `"author": "someone"` to `"author": "you"` + +### ❌ ID doesn't match filename +``` +❌ plugins/my-plugin.json: Plugin ID "different-name" doesn't match filename "my-plugin.json" +``` +**Fix**: Change `"id": "different-name"` to `"id": "my-plugin"` + +### ❌ Invalid version +``` +❌ plugins/example-plugin.json: Invalid version format: v1.0 +``` +**Fix**: Use semantic versioning like `"version": "1.0.0"` + +### ❌ Thumbnail too large +``` +❌ plugins/example-plugin.json: Thumbnail validation failed: Thumbnail exceeds 2MB limit (3.45MB) +``` +**Fix**: Compress your thumbnail to under 2MB + +### ❌ Thumbnail dimensions too large +``` +❌ plugins/example-plugin.json: Thumbnail validation failed: Thumbnail dimensions 1024x768 exceed 512x512 +``` +**Fix**: Resize your thumbnail to 512×512 or smaller + +## Example Valid Plugin JSON + +```json +{ + "id": "example-plugin", + "name": "Example Plugin", + "version": "1.0.0", + "description": "An example plugin demonstrating the plugin system capabilities", + "author": "litruv", + "repository": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example.git", + "thumbnail": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example/raw/branch/main/thumbnail.png", + "homepage": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example", + "tags": ["example", "demo"] +} +``` + +## Security Note + +**Never include sensitive information in plugin JSON files.** These files are public and will be served to all plugin host users. Do not include: +- API keys or tokens +- Passwords +- Private URLs or endpoints +- Personal information From 9c34554baddb0fb50983c7aea3dd88d6dccaeb68 Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Fri, 17 Apr 2026 02:53:03 +1000 Subject: [PATCH 09/28] Security: prevent unauthorized plugin modifications --- .gitea/scripts/validate-pr.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/.gitea/scripts/validate-pr.js b/.gitea/scripts/validate-pr.js index b32e044..b3b5e02 100644 --- a/.gitea/scripts/validate-pr.js +++ b/.gitea/scripts/validate-pr.js @@ -171,6 +171,29 @@ for (const file of changedFiles) { const content = await readFile(file, 'utf-8'); const plugin = JSON.parse(content); + // Check if this plugin already exists on main + let isModification = false; + let originalAuthor = null; + + try { + const originalContent = await readFile(`../base/${file}`, 'utf-8'); + const originalPlugin = JSON.parse(originalContent); + isModification = true; + originalAuthor = originalPlugin.author; + } catch { + // File doesn't exist on main, this is a new plugin + isModification = false; + } + + // If modifying existing plugin, MUST match original author + if (isModification) { + if (originalAuthor !== prAuthor) { + errors.push(`❌ ${file}: Cannot modify plugin owned by "${originalAuthor}" (you are "${prAuthor}")`); + continue; + } + console.log(` ℹ️ Modifying existing plugin by ${originalAuthor}`); + } + // Check required fields for (const field of REQUIRED_FIELDS) { if (!plugin[field]) { @@ -188,7 +211,7 @@ for (const file of changedFiles) { errors.push(`❌ ${file}: Invalid version format: ${plugin.version}`); } - // Check author matches PR creator (for new files) + // Check author matches PR creator if (plugin.author !== prAuthor) { errors.push(`❌ ${file}: Author "${plugin.author}" must match PR creator "${prAuthor}"`); } From 54b1b6efa6888680e2943a7d202ab3c9ab9cc9c2 Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Fri, 17 Apr 2026 02:53:31 +1000 Subject: [PATCH 10/28] Add ownership validation for plugin removal --- .gitea/scripts/validate-pr.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.gitea/scripts/validate-pr.js b/.gitea/scripts/validate-pr.js index b3b5e02..6629e2b 100644 --- a/.gitea/scripts/validate-pr.js +++ b/.gitea/scripts/validate-pr.js @@ -235,9 +235,21 @@ for (const file of changedFiles) { } } else if (isRemoved) { - // For removed files, we can't validate ownership easily in CI - // Just track that it was removed console.log(' 7. Thumbnail (optional): PNG/JPG/GIF, max 512x512, max 2MB'); removed.push(pluginId); - console.log(`🗑️ Removed: ${pluginId}`); + // For removed files, check the original author from main branch + try { + const originalContent = await readFile(`../base/${file}`, 'utf-8'); + const originalPlugin = JSON.parse(originalContent); + + if (originalPlugin.author !== prAuthor) { + errors.push(`❌ ${file}: Cannot remove plugin owned by "${originalPlugin.author}" (you are "${prAuthor}")`); + continue; + } + + removed.push(pluginId); + console.log(`🗑️ Removed: ${pluginId} (owned by ${prAuthor})`); + } catch (err) { + errors.push(`❌ ${file}: Could not verify ownership for removal: ${err.message}`); + } } } From 3db0e35e814dbc1c7677a4fc1d087ecfae62895d Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Fri, 17 Apr 2026 02:53:45 +1000 Subject: [PATCH 11/28] Update docs with ownership validation details --- VALIDATION_RULES.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/VALIDATION_RULES.md b/VALIDATION_RULES.md index 915fc6e..18ad351 100644 --- a/VALIDATION_RULES.md +++ b/VALIDATION_RULES.md @@ -87,9 +87,23 @@ You can include any additional fields for metadata: - The `author` field must match your Gitea username - This is checked automatically +### Modifying Plugins +- **You can only modify plugins where the ORIGINAL author (on main branch) is you** +- Even if you change the `author` field in your PR, validation checks against the original +- Attempting to modify someone else's plugin will fail validation with: + ``` + ❌ plugins/their-plugin.json: Cannot modify plugin owned by "them" (you are "you") + ``` + ### Removing Plugins -- You can only remove plugins where `author` matches your username -- Attempting to remove someone else's plugin will fail validation +- **You can only remove plugins where the ORIGINAL author (on main branch) is you** +- The validation checks the plugin's author from the main branch, not from your PR +- Attempting to remove someone else's plugin will fail validation with: + ``` + ❌ plugins/their-plugin.json: Cannot remove plugin owned by "them" (you are "you") + ``` + +**Security Note:** All ownership checks use the plugin data from the `main` branch, not from your PR. You cannot bypass ownership by modifying the `author` field. ## Validation Process From c99a10e1c2a80caec861e316e9cee507d8633b48 Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Fri, 17 Apr 2026 02:56:13 +1000 Subject: [PATCH 12/28] Add security documentation and commit author logging --- .gitea/workflows/validate-pr.yml | 22 ++++++++++++++++++++++ SECURITY.md | 0 2 files changed, 22 insertions(+) create mode 100644 SECURITY.md diff --git a/.gitea/workflows/validate-pr.yml b/.gitea/workflows/validate-pr.yml index a284687..baaa95d 100644 --- a/.gitea/workflows/validate-pr.yml +++ b/.gitea/workflows/validate-pr.yml @@ -43,6 +43,28 @@ jobs: exit 1 fi + - name: Verify commit authors match PR creator + run: | + cd pr + echo "🔍 Verifying commit authors..." + PR_USER="${{ github.event.pull_request.user.login }}" + echo "PR created by: $PR_USER" + + # Get all commits in this PR + COMMITS=$(git log --format="%H" origin/${{ github.base_ref }}..HEAD) + + for commit in $COMMITS; do + AUTHOR=$(git show -s --format='%an' $commit) + EMAIL=$(git show -s --format='%ae' $commit) + echo " Commit $commit by $AUTHOR <$EMAIL>" + + # This is a warning, not a hard block, since git authors can be configured differently + # The real security comes from PR creator authentication + done + + echo "✓ PR created by authenticated user: $PR_USER" + echo "Note: Validation will check plugin ownership against this authenticated user, not git commit authors" + - name: Validate PR (using trusted validation script from main) run: | node base/.gitea/scripts/validate-pr.js "${{ github.event.pull_request.user.login }}" "${{ steps.changed-files.outputs.files }}" diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..e69de29 From fc4c5fb1d122090fd3bdab6670c10bc2cb5a705c Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Fri, 17 Apr 2026 02:57:51 +1000 Subject: [PATCH 13/28] Add username prefix requirement for plugin filenames --- .gitea/scripts/validate-pr.js | 6 ++ SECURITY.md | 137 ++++++++++++++++++++++++++++++++++ VALIDATION_RULES.md | 38 +++++++--- 3 files changed, 171 insertions(+), 10 deletions(-) diff --git a/.gitea/scripts/validate-pr.js b/.gitea/scripts/validate-pr.js index 6629e2b..0362d4b 100644 --- a/.gitea/scripts/validate-pr.js +++ b/.gitea/scripts/validate-pr.js @@ -154,6 +154,12 @@ for (const file of changedFiles) { const pluginId = file.replace('plugins/', '').replace('.json', ''); + // Filename must be prefixed with username + if (!pluginId.startsWith(`${prAuthor}-`)) { + errors.push(`❌ ${file}: Filename must start with your username "${prAuthor}-" (e.g., "${prAuthor}-my-plugin.json")`); + continue; + } + // Check if file was added or removed let isAdded = false; let isRemoved = false; diff --git a/SECURITY.md b/SECURITY.md index e69de29..c11a3cb 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -0,0 +1,137 @@ +# Security Model + +## Authentication vs Git Configuration + +### How User Identity Works + +**Question:** Can't I just set my git username to anything and bypass validation? + +**Answer:** No. The validation uses **Gitea account authentication**, not git commit authors. + +#### Git Commit Author (NOT used for validation) +```bash +# This can be set to anything locally +git config user.name "anyone" +git config user.email "anyone@example.com" +git commit -m "My commit" +``` +These values are **NOT** used for plugin ownership validation. + +#### PR Creator (used for validation) +When you create a Pull Request: +1. You must be logged into your Gitea account +2. The PR is created through Gitea's authenticated API +3. `github.event.pull_request.user.login` = your authenticated Gitea username + +**This is what validation checks against.** + +### Example Attack Scenario (Prevented) + +❌ **Attacker tries:** +```bash +# Configure git to impersonate someone +git config user.name "victim" +git config user.email "victim@example.com" + +# Modify victim's plugin +vim plugins/victim-plugin.json +# Change author to "attacker" + +git commit -m "Update plugin" +git push + +# Create PR (must log into Gitea as "attacker" account) +``` + +✅ **What happens:** +1. PR is created by authenticated Gitea user: `attacker` +2. Validation script receives: `prAuthor = "attacker"` +3. Validation checks original plugin from main branch: `original.author = "victim"` +4. Check fails: `"victim" !== "attacker"` +5. **Result:** ❌ `Cannot modify plugin owned by "victim" (you are "attacker")` + +The git commit author is irrelevant - what matters is **who is logged into Gitea when creating the PR**. + +## Security Measures + +### 1. Dual Checkout +```yaml +- Checkout main branch → base/ (trusted validation scripts) +- Checkout PR branch → pr/ (untrusted user submissions) +``` +Validation script always runs from `base/`, never from PR branch. + +### 2. Infrastructure Protection +PRs that modify `.gitea/` directory are automatically rejected. + +### 3. Ownership Verification +All modifications/deletions check **original ownership from main branch**: +```javascript +// Check original plugin from main (base/) +const originalPlugin = readFile('../base/plugins/example.json'); + +// User can only modify if they own the original +if (originalPlugin.author !== prAuthor) { + reject("Cannot modify plugin owned by someone else"); +} +``` + +### 4. Author Field Validation +New plugins must have `author` field matching the PR creator's authenticated Gitea username. + +## What About Collaboration? + +**Q:** What if I want someone else to update my plugin? + +**A:** They cannot submit PRs directly. Options: +1. Transfer ownership by updating the `author` field yourself +2. They fork your plugin repo and create their own plugin entry +3. Add them as collaborators to your plugin repository (not the directory) + +## Trust Model + +### Trusted Components +- Main branch content (validated history) +- Gitea authentication system +- GitHub Actions runner environment +- Validation scripts on main branch + +### Untrusted Components +- PR branch content (user submissions) +- Git commit metadata (names, emails) +- PR descriptions and comments + +### Validation Flow +``` +1. User authenticates to Gitea → Trusted user identity +2. User creates PR → github.event.pull_request.user.login +3. Validation runs from main branch scripts → Trusted code +4. Checks plugin ownership against main → Trusted ownership data +5. Validates PR author matches plugin author → Secure check +``` + +## Reporting Security Issues + +If you find a security vulnerability in the plugin directory system: +1. **Do NOT create a public issue or PR** +2. Contact the repository maintainer directly +3. Provide detailed reproduction steps +4. Allow time for a fix before public disclosure + +## Additional Safeguards + +### Rate Limiting +Gitea's built-in rate limiting prevents spam PR attacks. + +### Branch Protection +The `main` branch is protected: +- Direct pushes disabled +- All changes via PR +- Auto-merge only after validation passes + +### Audit Trail +All changes are tracked in git history: +- Who created the PR (Gitea account) +- What changed (git diff) +- When it was merged (git commit timestamp) +- Why validation passed (CI logs) diff --git a/VALIDATION_RULES.md b/VALIDATION_RULES.md index 18ad351..af97ca3 100644 --- a/VALIDATION_RULES.md +++ b/VALIDATION_RULES.md @@ -14,7 +14,16 @@ All plugin submissions are automatically validated before being merged. PRs must - ❌ Cannot modify `.gitea/` infrastructure files - ⚠️ `plugins/index.json` is auto-generated - don't manually edit it -### 2. File Type +### 2. File Naming +- **Filename must be prefixed with your username** +- Format: `username-plugin-name.json` +- Example: `litruv-example-plugin.json` +- ❌ Invalid: `example-plugin.json` (missing username prefix) +- ❌ Invalid: `otheruser-plugin.json` (wrong username) + +This prevents naming conflicts and makes ownership visible. + +### 3. File Type - ✅ Only `.json` files are allowed in `plugins/` directory - ❌ No other file types (images, scripts, etc. must be in plugin repo) @@ -38,7 +47,8 @@ Every plugin JSON must have these fields: #### `id` (required) - Must match the filename -- Example: `example-plugin.json` → `"id": "example-plugin"` +- Example: `litruv-example-plugin.json` → `"id": "litruv-example-plugin"` +- Must include username prefix matching filename - Lowercase, hyphens allowed, no spaces #### `name` (required) @@ -134,9 +144,15 @@ node .gitea/scripts/validate-pr.js "your-username" "plugins/your-plugin.json" **Fix**: Change `"author": "someone"` to `"author": "you"` ### ❌ ID doesn't match filename +```litruv-my-plugin.json: Plugin ID "different-name" doesn't match filename "litruv-my-plugin.json" ``` -❌ plugins/my-plugin.json: Plugin ID "different-name" doesn't match filename "my-plugin.json" +**Fix**: Change `"id": "different-name"` to `"id": "litruv-my-plugin"` + +### ❌ Filename missing username prefix ``` +❌ plugins/my-plugin.json: Filename must start with your username "litruv-" (e.g., "litruv-my-plugin.json") +``` +**Fix**: Rename file from `my-plugin.json` to `litruv-my-plugin.json` and update the `id` field **Fix**: Change `"id": "different-name"` to `"id": "my-plugin"` ### ❌ Invalid version @@ -155,13 +171,7 @@ node .gitea/scripts/validate-pr.js "your-username" "plugins/your-plugin.json" ``` ❌ plugins/example-plugin.json: Thumbnail validation failed: Thumbnail dimensions 1024x768 exceed 512x512 ``` -**Fix**: Resize your thumbnail to 512×512 or smaller - -## Example Valid Plugin JSON - -```json -{ - "id": "example-plugin", +**Fix**: litruv-example-plugin", "name": "Example Plugin", "version": "1.0.0", "description": "An example plugin demonstrating the plugin system capabilities", @@ -173,6 +183,14 @@ node .gitea/scripts/validate-pr.js "your-username" "plugins/your-plugin.json" } ``` +Filename: `litruv-example-plugin.json"author": "litruv", + "repository": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example.git", + "thumbnail": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example/raw/branch/main/thumbnail.png", + "homepage": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example", + "tags": ["example", "demo"] +} +``` + ## Security Note **Never include sensitive information in plugin JSON files.** These files are public and will be served to all plugin host users. Do not include: From bbc01a37f184b07457e6dd90ee6bc5a8ea572413 Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Fri, 17 Apr 2026 02:58:13 +1000 Subject: [PATCH 14/28] Update plugin IDs and filenames to include username prefix --- plugins/{example-plugin-2.json => litruv-example-plugin-2.json} | 0 plugins/{example-plugin.json => litruv-example-plugin.json} | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename plugins/{example-plugin-2.json => litruv-example-plugin-2.json} (100%) rename plugins/{example-plugin.json => litruv-example-plugin.json} (94%) diff --git a/plugins/example-plugin-2.json b/plugins/litruv-example-plugin-2.json similarity index 100% rename from plugins/example-plugin-2.json rename to plugins/litruv-example-plugin-2.json diff --git a/plugins/example-plugin.json b/plugins/litruv-example-plugin.json similarity index 94% rename from plugins/example-plugin.json rename to plugins/litruv-example-plugin.json index 0fd5867..efae59e 100644 --- a/plugins/example-plugin.json +++ b/plugins/litruv-example-plugin.json @@ -1,5 +1,5 @@ { - "id": "example-plugin", + "id": "litruv-example-plugin", "name": "Example Plugin", "version": "1.0.0", "description": "An example plugin demonstrating the plugin system capabilities", From ff4b685831fa7d5eede9a81bbf5f56caece93c80 Mon Sep 17 00:00:00 2001 From: Gitea Actions Date: Thu, 16 Apr 2026 16:58:22 +0000 Subject: [PATCH 15/28] Auto-update plugin index [skip ci] --- plugins/index.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/index.json b/plugins/index.json index 36c211b..27a67b6 100644 --- a/plugins/index.json +++ b/plugins/index.json @@ -1,8 +1,8 @@ { "version": "1.0.0", - "updatedAt": "2026-04-16T16:48:14.146Z", + "updatedAt": "2026-04-16T16:58:22.727Z", "plugins": [ - "example-plugin", - "example-plugin-2" + "litruv-example-plugin", + "litruv-example-plugin-2" ] } From fcf4c490e0a7cebcdd18e6b57866d196636cc10d Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Fri, 17 Apr 2026 02:58:34 +1000 Subject: [PATCH 16/28] Fix example-plugin-2 ID --- plugins/litruv-example-plugin-2.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/litruv-example-plugin-2.json b/plugins/litruv-example-plugin-2.json index ec79b81..7cc7cf4 100644 --- a/plugins/litruv-example-plugin-2.json +++ b/plugins/litruv-example-plugin-2.json @@ -1,5 +1,5 @@ { - "id": "example-plugin-2", + "id": "litruv-example-plugin-2", "name": "Example Plugin 2", "version": "1.0.0", "description": "A second example plugin demonstrating additional capabilities", From 63c4b4ca104ce5007248b35c132a2f9e36f8aec1 Mon Sep 17 00:00:00 2001 From: Gitea Actions Date: Thu, 16 Apr 2026 16:58:43 +0000 Subject: [PATCH 17/28] Auto-update plugin index [skip ci] --- plugins/index.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/index.json b/plugins/index.json index 27a67b6..315a12b 100644 --- a/plugins/index.json +++ b/plugins/index.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "updatedAt": "2026-04-16T16:58:22.727Z", + "updatedAt": "2026-04-16T16:58:43.725Z", "plugins": [ "litruv-example-plugin", "litruv-example-plugin-2" From 114c60f708c7516ce75d7738ab66ce61d1082fbe Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Fri, 17 Apr 2026 03:02:34 +1000 Subject: [PATCH 18/28] Fix validation script path --- .gitea/workflows/validate-pr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/validate-pr.yml b/.gitea/workflows/validate-pr.yml index baaa95d..7c68883 100644 --- a/.gitea/workflows/validate-pr.yml +++ b/.gitea/workflows/validate-pr.yml @@ -67,8 +67,8 @@ jobs: - name: Validate PR (using trusted validation script from main) run: | - node base/.gitea/scripts/validate-pr.js "${{ github.event.pull_request.user.login }}" "${{ steps.changed-files.outputs.files }}" - working-directory: pr + cd pr + node ../base/.gitea/scripts/validate-pr.js "${{ github.event.pull_request.user.login }}" "${{ steps.changed-files.outputs.files }}" - name: Auto-merge PR if: success() From 6f7dea6b0357365b052620b9012ec832034e5058 Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Fri, 17 Apr 2026 03:07:54 +1000 Subject: [PATCH 19/28] Fix validation script working directory --- .gitea/scripts/validate-pr.js | 12 ++++++++---- .gitea/workflows/validate-pr.yml | 7 +++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.gitea/scripts/validate-pr.js b/.gitea/scripts/validate-pr.js index 0362d4b..de99ec4 100644 --- a/.gitea/scripts/validate-pr.js +++ b/.gitea/scripts/validate-pr.js @@ -160,12 +160,16 @@ for (const file of changedFiles) { continue; } + // Adjust file paths based on working directory + const prFilePath = process.env.PR_FILES_DIR ? `${process.env.PR_FILES_DIR}/${file.replace('plugins/', '')}` : file; + const baseFilePath = file; + // Check if file was added or removed let isAdded = false; let isRemoved = false; try { - await access(file); + await access(prFilePath); isAdded = true; } catch { isRemoved = true; @@ -174,7 +178,7 @@ for (const file of changedFiles) { if (isAdded) { // Validate added/modified plugin try { - const content = await readFile(file, 'utf-8'); + const content = await readFile(prFilePath, 'utf-8'); const plugin = JSON.parse(content); // Check if this plugin already exists on main @@ -182,7 +186,7 @@ for (const file of changedFiles) { let originalAuthor = null; try { - const originalContent = await readFile(`../base/${file}`, 'utf-8'); + const originalContent = await readFile(baseFilePath, 'utf-8'); const originalPlugin = JSON.parse(originalContent); isModification = true; originalAuthor = originalPlugin.author; @@ -243,7 +247,7 @@ for (const file of changedFiles) { } else if (isRemoved) { // For removed files, check the original author from main branch try { - const originalContent = await readFile(`../base/${file}`, 'utf-8'); + const originalContent = await readFile(baseFilePath, 'utf-8'); const originalPlugin = JSON.parse(originalContent); if (originalPlugin.author !== prAuthor) { diff --git a/.gitea/workflows/validate-pr.yml b/.gitea/workflows/validate-pr.yml index 7c68883..04c1821 100644 --- a/.gitea/workflows/validate-pr.yml +++ b/.gitea/workflows/validate-pr.yml @@ -67,12 +67,15 @@ jobs: - name: Validate PR (using trusted validation script from main) run: | - cd pr - node ../base/.gitea/scripts/validate-pr.js "${{ github.event.pull_request.user.login }}" "${{ steps.changed-files.outputs.files }}" + cd base + node .gitea/scripts/validate-pr.js "${{ github.event.pull_request.user.login }}" "${{ steps.changed-files.outputs.files }}" + env: + PR_FILES_DIR: ../pr/plugins - name: Auto-merge PR if: success() run: | + cd pr echo "✅ Validation passed - Auto-merging PR" git config user.name "GitHub Actions" git config user.email "actions@github.com" From 75355cd41f87403f465a8525cc877c05af7e23fb Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Fri, 17 Apr 2026 03:11:30 +1000 Subject: [PATCH 20/28] Fix thumbnail validation scope issue --- .gitea/scripts/validate-pr.js | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.gitea/scripts/validate-pr.js b/.gitea/scripts/validate-pr.js index de99ec4..9096d4d 100644 --- a/.gitea/scripts/validate-pr.js +++ b/.gitea/scripts/validate-pr.js @@ -104,24 +104,7 @@ const changedFiles = process.argv[3]?.split('\n').filter(f => f.trim()) || []; if (!prAuthor) { console.error('❌ Error: PR author not provided'); process.exit(1); -}// Check thumbnail if provided (optional) - if (plugin.thumbnail) { - try { - new URL(plugin.thumbnail); - - // Validate thumbnail meets requirements - try { - const thumbInfo = await validateThumbnail(plugin.thumbnail); - console.log(` ✓ Thumbnail: ${thumbInfo.format} ${thumbInfo.width}x${thumbInfo.height} (${(thumbInfo.size / 1024).toFixed(1)}KB)`); - } catch (thumbErr) { - errors.push(`❌ ${file}: Thumbnail validation failed: ${thumbErr.message}`); - } - } catch { - errors.push(`❌ ${file}: Invalid thumbnail URL: ${plugin.thumbnail}`); - } - } - - +} console.log(`\n🔍 Validating PR from: ${prAuthor}`); console.log(`📝 Changed files: ${changedFiles.length}`); @@ -235,6 +218,23 @@ for (const file of changedFiles) { } } + // Check thumbnail if provided (optional) + if (plugin.thumbnail) { + try { + new URL(plugin.thumbnail); + + // Validate thumbnail meets requirements + try { + const thumbInfo = await validateThumbnail(plugin.thumbnail); + console.log(` ✓ Thumbnail: ${thumbInfo.format} ${thumbInfo.width}x${thumbInfo.height} (${(thumbInfo.size / 1024).toFixed(1)}KB)`); + } catch (thumbErr) { + errors.push(`❌ ${file}: Thumbnail validation failed: ${thumbErr.message}`); + } + } catch { + errors.push(`❌ ${file}: Invalid thumbnail URL: ${plugin.thumbnail}`); + } + } + if (errors.length === 0) { added.push(pluginId); console.log(`✅ Valid plugin: ${plugin.name}`); From dff9c6c8c8731020c2c1fe579d503a182add587d Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 16 Apr 2026 17:11:50 +0000 Subject: [PATCH 21/28] add-example-plugin-3 --- plugins/litruv-example-plugin-3.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 plugins/litruv-example-plugin-3.json diff --git a/plugins/litruv-example-plugin-3.json b/plugins/litruv-example-plugin-3.json new file mode 100644 index 0000000..185d83f --- /dev/null +++ b/plugins/litruv-example-plugin-3.json @@ -0,0 +1,10 @@ +{ + "id": "litruv-example-plugin-3", + "name": "Example Plugin 3", + "version": "1.0.0", + "description": "A third example plugin demonstrating async operations and data storage", + "author": "litruv", + "repository": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example3.git", + "homepage": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example3", + "tags": ["example", "async", "storage"] +} From 9a6344ffc53e8405eaefdf45ba10ee8cc3858a27 Mon Sep 17 00:00:00 2001 From: litruv Date: Fri, 17 Apr 2026 03:12:45 +1000 Subject: [PATCH 22/28] Delete plugins/litruv-example-plugin-3.json --- plugins/litruv-example-plugin-3.json | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 plugins/litruv-example-plugin-3.json diff --git a/plugins/litruv-example-plugin-3.json b/plugins/litruv-example-plugin-3.json deleted file mode 100644 index 185d83f..0000000 --- a/plugins/litruv-example-plugin-3.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "id": "litruv-example-plugin-3", - "name": "Example Plugin 3", - "version": "1.0.0", - "description": "A third example plugin demonstrating async operations and data storage", - "author": "litruv", - "repository": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example3.git", - "homepage": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example3", - "tags": ["example", "async", "storage"] -} From 22975cdf93e207333b34ede77661aba1efe430ce Mon Sep 17 00:00:00 2001 From: Gitea Actions Date: Thu, 16 Apr 2026 17:12:52 +0000 Subject: [PATCH 23/28] Auto-update plugin index [skip ci] --- plugins/index.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/index.json b/plugins/index.json index 315a12b..9a076d0 100644 --- a/plugins/index.json +++ b/plugins/index.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "updatedAt": "2026-04-16T16:58:43.725Z", + "updatedAt": "2026-04-16T17:12:52.584Z", "plugins": [ "litruv-example-plugin", "litruv-example-plugin-2" From a6fa17e2855a0dd5a4d048e9d93132b61d3176d5 Mon Sep 17 00:00:00 2001 From: litruv Date: Fri, 17 Apr 2026 03:14:22 +1000 Subject: [PATCH 24/28] Delete plugins/litruv-example-plugin-2.json --- plugins/litruv-example-plugin-2.json | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 plugins/litruv-example-plugin-2.json diff --git a/plugins/litruv-example-plugin-2.json b/plugins/litruv-example-plugin-2.json deleted file mode 100644 index 7cc7cf4..0000000 --- a/plugins/litruv-example-plugin-2.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "id": "litruv-example-plugin-2", - "name": "Example Plugin 2", - "version": "1.0.0", - "description": "A second example plugin demonstrating additional capabilities", - "author": "litruv", - "repository": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example2.git", - "downloadUrl": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example2/archive/main.zip", - "homepage": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example2", - "tags": ["example", "demo", "filtering", "validation"], - "addedDate": "2026-04-17T00:00:00.000Z" -} \ No newline at end of file From 70af2c65eea74e396ca4ed02116bf0aa5d1a1be7 Mon Sep 17 00:00:00 2001 From: litruv Date: Fri, 17 Apr 2026 03:14:27 +1000 Subject: [PATCH 25/28] Delete plugins/litruv-example-plugin.json --- plugins/litruv-example-plugin.json | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 plugins/litruv-example-plugin.json diff --git a/plugins/litruv-example-plugin.json b/plugins/litruv-example-plugin.json deleted file mode 100644 index efae59e..0000000 --- a/plugins/litruv-example-plugin.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "litruv-example-plugin", - "name": "Example Plugin", - "version": "1.0.0", - "description": "An example plugin demonstrating the plugin system capabilities", - "author": "litruv", - "repository": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example.git", - "thumbnail": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example/raw/branch/main/thumbnail.png", - "downloadUrl": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example/archive/main.zip", - "homepage": "http://synbox.ruv.wtf:8418/litruv/Plugin-Example", - "tags": ["example", "demo"], - "addedDate": "2026-04-17T00:00:00.000Z" -} From 7aba8dbc61fa204cd2ff342d9514c28c00ace1c3 Mon Sep 17 00:00:00 2001 From: Gitea Actions Date: Thu, 16 Apr 2026 17:14:36 +0000 Subject: [PATCH 26/28] Auto-update plugin index [skip ci] --- plugins/index.json | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/index.json b/plugins/index.json index 9a076d0..f09e9c8 100644 --- a/plugins/index.json +++ b/plugins/index.json @@ -1,8 +1,5 @@ { "version": "1.0.0", - "updatedAt": "2026-04-16T17:12:52.584Z", - "plugins": [ - "litruv-example-plugin", - "litruv-example-plugin-2" - ] + "updatedAt": "2026-04-16T17:14:35.921Z", + "plugins": [] } From 2c93df73078984ff8e12104d26dffa95f01ac1e6 Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Fri, 17 Apr 2026 03:16:47 +1000 Subject: [PATCH 27/28] Add token authentication for auto-merge --- .gitea/workflows/validate-pr.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/validate-pr.yml b/.gitea/workflows/validate-pr.yml index 04c1821..eeb5aeb 100644 --- a/.gitea/workflows/validate-pr.yml +++ b/.gitea/workflows/validate-pr.yml @@ -77,11 +77,22 @@ jobs: run: | cd pr echo "✅ Validation passed - Auto-merging PR" - git config user.name "GitHub Actions" - git config user.email "actions@github.com" + + # Configure git with token authentication + git config user.name "gitea-actions[bot]" + git config user.email "gitea-actions[bot]@users.noreply.gitea.io" + + # Set up authenticated remote URL + git remote set-url origin "http://oauth2:${{ secrets.AUTO_MERGE_TOKEN }}@synbox.ruv.wtf:8418/litruv/Plugin-Directory.git" + + # Fetch and merge git fetch origin ${{ github.event.pull_request.head.ref }} git checkout main git merge --squash origin/${{ github.event.pull_request.head.ref }} git commit -m "${{ github.event.pull_request.title }}" + + # Push changes git push origin main + + # Delete the PR branch git push origin --delete ${{ github.event.pull_request.head.ref }} From 4dc6d0919ee85ed3056f654faf43ea694797758b Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Fri, 17 Apr 2026 03:20:21 +1000 Subject: [PATCH 28/28] Use github.token for auto-merge authentication --- .gitea/workflows/validate-pr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/validate-pr.yml b/.gitea/workflows/validate-pr.yml index eeb5aeb..c69ffd1 100644 --- a/.gitea/workflows/validate-pr.yml +++ b/.gitea/workflows/validate-pr.yml @@ -82,8 +82,8 @@ jobs: git config user.name "gitea-actions[bot]" git config user.email "gitea-actions[bot]@users.noreply.gitea.io" - # Set up authenticated remote URL - git remote set-url origin "http://oauth2:${{ secrets.AUTO_MERGE_TOKEN }}@synbox.ruv.wtf:8418/litruv/Plugin-Directory.git" + # Set up authenticated remote URL using github.token + git remote set-url origin "http://oauth2:${{ github.token }}@synbox.ruv.wtf:8418/litruv/Plugin-Directory.git" # Fetch and merge git fetch origin ${{ github.event.pull_request.head.ref }}