Header-only control token, masked secrets, loopback default (v0.9.9)
Finishes the small items of the security audit in docs/prs/2026-07-08-PR#000: TODO 3, 4 and 7. The three mutating endpoints of BuildsApiController no longer accept the control token as a `token` query parameter — only the X-GitTally-Token header, which the bundled UI has always used. URLs end up in access logs, proxy logs, browser history and Referer headers, and the token never expires, so a historical log capture would yield a valid credential. `config:print` masks git.token as `***` on both the raw and the --full path and names the new --show-secrets flag in a leading YAML comment, so the output stays parseable when piped. The setup script points at --show-secrets where it used to steer the operator to the plain token. `server.bindAddress` now defaults to 127.0.0.1: neither the UI nor the API authenticates read access, so reaching GitTally should require the host's reverse proxy. Existing .gittally.yml files keep their explicit value; the managed nginx container needs `0.0.0.0` set deliberately, which is noted in the release notes, docs/configuration.md and docs/deployment.md. Released as v0.9.9, which also carries the previous two commits. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -24,8 +24,10 @@ import java.nio.file.StandardOpenOption
|
||||
|
||||
/**
|
||||
* JSON API over build results and running builds, replacing the legacy
|
||||
* `/control/…` endpoints. Mutating endpoints are guarded by the control token
|
||||
* (header [TOKEN_HEADER] or parameter `token`), like the legacy cancel token.
|
||||
* `/control/…` endpoints. Mutating endpoints are guarded by the control token,
|
||||
* like the legacy cancel token — in the header [TOKEN_HEADER] only: a token in
|
||||
* the query string would end up in access logs, browser history and `Referer`
|
||||
* headers, and it never expires.
|
||||
*/
|
||||
@RestController
|
||||
class BuildsApiController(
|
||||
@@ -91,9 +93,8 @@ class BuildsApiController(
|
||||
fun restart(
|
||||
@RequestParam branch: String,
|
||||
@RequestHeader(name = TOKEN_HEADER, required = false) headerToken: String?,
|
||||
@RequestParam(name = "token", required = false) paramToken: String?,
|
||||
): ResponseEntity<Any> {
|
||||
rejectBadToken(headerToken ?: paramToken)?.let { return it }
|
||||
rejectBadToken(headerToken)?.let { return it }
|
||||
val commit =
|
||||
repository.latestFor(branch)?.commit
|
||||
?: gitService.originHeadCommit(branch, workingDir)
|
||||
@@ -116,9 +117,8 @@ class BuildsApiController(
|
||||
fun cancel(
|
||||
@PathVariable artifactKey: String,
|
||||
@RequestHeader(name = TOKEN_HEADER, required = false) headerToken: String?,
|
||||
@RequestParam(name = "token", required = false) paramToken: String?,
|
||||
): ResponseEntity<Any> {
|
||||
rejectBadToken(headerToken ?: paramToken)?.let { return it }
|
||||
rejectBadToken(headerToken)?.let { return it }
|
||||
if (!buildExecutor.cancel(artifactKey)) {
|
||||
return notFound("no queued or running build with artifact key '$artifactKey'")
|
||||
}
|
||||
@@ -130,9 +130,8 @@ class BuildsApiController(
|
||||
fun delete(
|
||||
@PathVariable artifactKey: String,
|
||||
@RequestHeader(name = TOKEN_HEADER, required = false) headerToken: String?,
|
||||
@RequestParam(name = "token", required = false) paramToken: String?,
|
||||
): ResponseEntity<Any> {
|
||||
rejectBadToken(headerToken ?: paramToken)?.let { return it }
|
||||
rejectBadToken(headerToken)?.let { return it }
|
||||
if (!repository.delete(artifactKey)) {
|
||||
return notFound("no build with artifact key '$artifactKey'")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user