Print every time in UTC, whatever the time zone (closes #169)
check / check (push) Waiting to run
check / check (push) Waiting to run
secret list, secret version list, secret unlocker list and secret info printed times in the machine's time zone without naming it, so one vault showed different times on two machines. Entry() now sets time.Local to UTC before anything else, as AGENTS.md policy 6 asks, so every time the program prints or writes is in UTC: --json times end in Z, and a new version's name takes the UTC date. The README says so. A test runs the built secret list with TZ=America/New_York and checks the table and the JSON against the UTC time set on the secret's current file. Rule suppressed: gosmopolitan, which flags any use of time.Local. Model: opus-5-5
This commit was merged in pull request #187.
This commit is contained in:
@@ -67,6 +67,10 @@ and unlocker set. This allows for complete isolation between different contexts
|
||||
|
||||
## Commands Reference
|
||||
|
||||
Every time `secret` prints, in a table or in `--json` output, is in UTC,
|
||||
whatever `TZ` or the system's time zone is set to. The date in a version's name
|
||||
is the UTC date too.
|
||||
|
||||
### Confirmation Before Removal
|
||||
|
||||
`secret rm`, `secret version rm`, `secret vault remove` and
|
||||
|
||||
@@ -3,6 +3,9 @@ package cli_test
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -125,6 +128,57 @@ func TestMovedSecretKeepsItsTimes(t *testing.T) {
|
||||
"updated %s, then %s", beforeMove.UpdatedAt, afterMove.UpdatedAt)
|
||||
}
|
||||
|
||||
// TestListedTimesAreUTC is a regression test for
|
||||
// https://git.eeqj.de/sneak/secret/issues/169, where times were printed
|
||||
// in the local time zone. It runs `secret list` on the built binary with
|
||||
// TZ set to New York, where the secret's last update, 03:04:05 UTC on 2
|
||||
// January, is 22:04:05 the day before.
|
||||
func TestListedTimesAreUTC(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
stateDir := t.TempDir()
|
||||
|
||||
vlt, err := vault.CreateVault(
|
||||
afero.NewOsFs(), stateDir, "default", testMnemonicBuffer(t), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
vlt.Mnemonic = testMnemonicBuffer(t)
|
||||
err = vlt.AddSecret("x", memguard.NewBufferFromBytes([]byte("one")), false)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated := time.Date(2026, time.January, 2, 3, 4, 5, 0, time.UTC)
|
||||
current := filepath.Join(stateDir, "vaults.d", "default", "secrets.d", "x",
|
||||
"current")
|
||||
require.NoError(t, os.Chtimes(current, updated, updated))
|
||||
|
||||
tests := []struct {
|
||||
flags []string
|
||||
want string
|
||||
}{
|
||||
{flags: nil, want: "2026-01-02 03:04"},
|
||||
{
|
||||
flags: []string{"--json"},
|
||||
want: `"updated_at": "2026-01-02T03:04:05Z"`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
args := append([]string{"list"}, tt.flags...)
|
||||
|
||||
//nolint:gosec // G204: test executes the freshly built secret binary
|
||||
cmd := exec.CommandContext(t.Context(), secretBinaryPath(t), args...)
|
||||
cmd.Env = []string{
|
||||
secret.EnvStateDir + "=" + stateDir,
|
||||
"TZ=America/New_York",
|
||||
"PATH=" + os.Getenv("PATH"),
|
||||
}
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
require.NoError(t, err, "%v: %s", args, output)
|
||||
assert.Contains(t, string(output), tt.want, "%v", args)
|
||||
}
|
||||
}
|
||||
|
||||
// listSecrets returns the output of `secret list`, or of
|
||||
// `secret list --json` when jsonOutput is set.
|
||||
func listSecrets(t *testing.T, c *cli.Instance, jsonOutput bool) string {
|
||||
|
||||
@@ -2,6 +2,7 @@ package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/awnumar/memguard"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -14,6 +15,10 @@ import (
|
||||
// every memguard buffer before it returns, so the caller must do nothing
|
||||
// but exit with the code.
|
||||
func Entry() int {
|
||||
// Every time secret prints or writes is in UTC, whatever TZ or the
|
||||
// system's time zone is set to.
|
||||
time.Local = time.UTC //nolint:gosmopolitan // this makes local time UTC
|
||||
|
||||
raiseLockedMemoryLimit()
|
||||
|
||||
// On SIGINT or SIGTERM memguard runs this function, wipes every buffer
|
||||
|
||||
Reference in New Issue
Block a user