Files
werkator/src/test/kotlin/de/hoennig/gittally/commands/ConfigPrintCommandTest.kt
T
mhoennigandClaude a4c995592f 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>
2026-08-11 07:38:33 +02:00

75 lines
2.5 KiB
Kotlin

package de.hoennig.gittally.commands
import de.hoennig.gittally.config.ConfigLoader
import de.hoennig.gittally.config.GitConfig
import de.hoennig.gittally.config.GitTallyConfig
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.string.shouldNotContain
import io.mockk.clearMocks
import io.mockk.every
import io.mockk.mockk
class ConfigPrintCommandTest : FunSpec() {
private val yamlWriter = ConfigLoader()
private val configLoader = mockk<ConfigLoader>()
private val command = ConfigPrintCommand(configLoader)
private val rawConfig =
mapOf<String, Any?>(
"git" to mapOf("account" to "ci-user", "token" to "s3cr3t-token"),
"server" to mapOf("port" to 18080),
)
init {
beforeEach {
clearMocks(configLoader)
every { configLoader.toYaml(any()) } answers { yamlWriter.toYaml(firstArg()) }
every { configLoader.loadRaw() } returns rawConfig
every { configLoader.load() } returns GitTallyConfig(git = GitConfig(account = "ci-user", token = "s3cr3t-token"))
command.full = false
command.showSecrets = false
}
test("masks the git token by default") {
val output = captureConsole { command.run() }.stdout
output shouldNotContain "s3cr3t-token"
output shouldContain "***"
output shouldContain "# git.token is masked"
output shouldContain "account: \"ci-user\""
}
test("masks the git token with --full as well") {
command.full = true
val output = captureConsole { command.run() }.stdout
output shouldNotContain "s3cr3t-token"
output shouldContain "***"
}
test("prints the git token with --show-secrets") {
command.showSecrets = true
val output = captureConsole { command.run() }.stdout
output shouldContain "s3cr3t-token"
output shouldNotContain "masked"
}
test("prints the git token with --full --show-secrets") {
command.full = true
command.showSecrets = true
captureConsole { command.run() }.stdout shouldContain "s3cr3t-token"
}
test("says nothing about masking when no token is configured") {
every { configLoader.loadRaw() } returns mapOf("server" to mapOf("port" to 18080))
captureConsole { command.run() }.stdout shouldNotContain "masked"
}
}
}