codeberg4s is a client library. It holds an API token in memory, puts it in an
Authorization header, and builds request paths out of values a caller
supplies. Almost everything that could go wrong here falls into one of those
three sentences.
Email balyszyn@gmail.com. Do not open a public issue.
Include, as far as you can:
- what an attacker gains, and what they need already to get it;
- the affected version or commit;
- the operation id or method involved, if there is one;
- a minimal reproduction — Scala that compiles is ideal;
- a redacted transcript. If your evidence contains a real token, replace it with a placeholder and say so. Do not send working credentials; a report is not worth creating a second incident.
If the repository's host offers private vulnerability reporting (GitHub's "Report a vulnerability" button, for instance), that works too and reaches the same person.
There is no bug bounty and no published PGP key. If you need an encrypted channel, say so in a first message containing nothing sensitive and one will be arranged.
This project is maintained by one person, so these are commitments that can actually be kept rather than aspirational ones:
| Stage | Target |
|---|---|
| Acknowledgement that the report arrived | 7 days |
| An assessment — is it a vulnerability, and how severe | 14 days |
| A fix released, for anything credible and high severity | 30 days |
| Public disclosure | after a fix ships, or 90 days, whichever is first |
If you have not heard anything after 14 days, assume the mail went astray and send it again. Credit is given in the changelog unless you ask otherwise.
| Version | Supported |
|---|---|
0.1.0-SNAPSHOT (unreleased) |
this is the development line; fixes land on main |
Nothing has been released yet. Once 0.1.0 ships, the policy is: fixes go
to the latest patch of the current minor, and — while the major version is 0,
under the Early SemVer scheme this project declares — the previous minor gets a
backport only if the fix is small and the release is recent. Pinning an old
0.x is not a supported way to avoid an upgrade.
Because the library is published as five artifacts under one version, a security fix is released across all five together even when only one changed.
Anything reachable through the published API of the five artifacts.
The library's central promise is that a token cannot escape through a
diagnostic path. ApiToken renders as *** in toString and in string
interpolation, reveal is the only accessor, CallContext holds a URI that
was redacted before the context was built, and CodebergException's message is
CodebergError.describe, which is assembled only from that redacted context
and from server-supplied text. There are tests asserting each of these, and
property suites over the rendering paths.
Any way to get credential material out through one of these is a vulnerability, not a bug, however unlikely the path:
- a token, password or basic-auth header appearing in a
toString, adescribe, an exception message, a stack trace or a telemetry callback; - a credential surviving in a URI, a query parameter or a header that reaches
CallContext, a log line or an error body; - a redaction that can be defeated by a crafted server response — an error body echoed back into a message, for instance;
- a credential written to disk, to a temporary file, or into a coverage or debugging artifact.
Report these privately even if the path looks theoretical.
Owners, repository names, branch names, labels, paths and page sizes are opaque
types with Either-returning smart constructors, and those constructors are a
security boundary, not a convenience. A value that escapes validation and
changes the request path — path traversal out of /repos/{owner}/{repo}/…,
a smuggled query parameter, a CRLF injected into a header — is in scope.
- Anything that would cause a request to be sent to a host other than the
configured
BaseUri, including redirect handling. - Anything that weakens TLS verification.
- A retry of a non-idempotent operation.
POSTandPATCHthat create or edit are deliberately never retried; a change that makes one retryable can file the same issue twice or double a payment-like side effect.
- A crafted 2xx payload that causes unbounded memory growth, non-terminating
decoding, or an exception that escapes the
CodebergErrorchannel. - A
Linkheader that drives a pagination walk into an infinite loop. Note the documented guard: a walk stops when the server stops offering a next page, and callers are told to guard on an empty page as well.
A known vulnerability in sttp client4 or upickle that this library exposes.
The dependency surface is deliberately two libraries; codeberg4s-domain has
none at all.
- Vulnerabilities in Forgejo, Gitea or codeberg.org. Report those upstream. Their trackers are the right place; this library only calls them.
- Anything requiring the attacker to already run code in your process. A heap dump contains the token; that is what holding a credential in memory means, and no library-level mitigation changes it.
- Configuration you chose. Pointing
BaseUriat a host you do not trust, loggingtoken.revealyourself, or committing a token to a repository. - Rate limiting and denial of service against a remote instance caused by
your own call volume. The retry policy is bounded and honours
Retry-After; driving a walk over ten thousand pages is a decision the caller makes. - Known limitations already documented. Two operations —
DownloadActionArtifactandrepoGetActionRunLogs, reachable asclient.downloads— hold the whole archive in memory; the library does not stream, and attachment streaming above 50 MB is explicitly out of scope for v1. A large artifact exhausting the heap is documented behaviour. If you can make it happen with a small request, that is a report. - Missing hardening with no exploit path, such as the absence of certificate pinning.
Stated so you know what to test against, not as a claim of safety:
-Werrorwith-Wunused:all,-Wvalue-discardand-Wnonunit-statement; Scalafix bansnull,throw,return,var, casts and unsafeOptionaccess outright.- No recoverable failure is thrown: failures are
CodebergError, and a barenew Exceptionin production code fails./verify.sh. - Redaction is tested directly, including property suites asserting that no rendering path emits a credential.
- Two dependencies, both widely used, both pinned in
build.mill. - Released artifacts are PGP-signed and built by
.github/workflows/release.ymlfrom a tagged commit, after the same./verify.shevery push runs. Signing keys and Sonatype tokens live only in repository secrets, and are passed to Mill as environment variables so they never reach a process listing or a build log. SeeRELEASING.md.
Maven Central is immutable: a published version cannot be altered or withdrawn.
The response is to release a fixed version immediately, mark the bad one in
CHANGELOG.md, and — if a credential was exposed — rotate it first, because
rotation is the only mitigation that does not depend on anyone upgrading.
RELEASING.md § "When a release is bad" has the procedure.