Skip to content

fix: a credential is recorded where it was meant to be and nowhere else - #66

Merged
donislawdev merged 1 commit into
mainfrom
security/secret-in-manifest
Sep 6, 2026
Merged

fix: a credential is recorded where it was meant to be and nowhere else#66
donislawdev merged 1 commit into
mainfrom
security/secret-in-manifest

Conversation

@donislawdev

Copy link
Copy Markdown
Owner

Finding S4 of the outside security review, confirmed here before anything was written.

The password of a locked archive was in the manifest twice.

  • Under the file's own properties, on purpose. archive.go says why beside that setting: a locked fixture nobody can open checks nothing. No argument, and this PR does not touch it.
  • Inside run.command, which records the whole command line as typed. That one was a side effect.

run.command is the line people copy - into a bug report, into a README, into a commit beside a set of fixtures. It reads like metadata rather than like fixture data, so it is not treated with the same care, and the two places were one accident apart.

What changed

The recorded command now reads --set password=***, in both shapes the flag package takes (--set password=x and --set=password=x). The deliberate copy is untouched, and the guard asserts that too - a fix that removed both would trade a leak for an archive nothing can open.

Which properties are credentials is declared where the property is declared, so a second one added later does not have to go looking for the places that care.

A manifest that carries one is written 0600 rather than 0644. Every other manifest and every generated file keeps the mode it had: this tool exists to produce files somebody else's CI will read, which is exactly why .golangci.yml turns gosec's permission rules off here. Windows has no permission bits, so nothing changes there.

internal/manifest keeps its own copy of the secret names and a guard compares the two against the registry. That package records what a run produced and knows nothing about formats - asking the registry from inside it would tie the record to it and would answer "no secrets" quietly in a process that registered none.

Behaviour change worth knowing

run.command is a different string now. If you compare that field between runs, it will differ. It is in CHANGELOG.md.

Guards

Three, and one of them is proven differently on purpose. TestAManifestCarryingACredentialIsWrittenForItsOwner cannot be observed on Windows - no permission bits - and the mutation runner runs there, so it would score NOT CAUGHT about a healthy guard. It was broken by hand instead: 0o600 changed to 0o666, guard binary cross compiled for linux/amd64 and run in a container against the real repository, red naming the case while the two that must stay 0644 stayed green. Recorded in provenByProbe. The half that runs everywhere has a mutation.

Two other mutations, both caught, plus three existing entries repaired rather than counted after this code moved under them.

🤖 Generated with Claude Code

The password of a locked archive was in the manifest twice. Under the
file's own properties, where it is written on purpose - archive.go says
why beside that setting, and a locked fixture nobody can open checks
nothing. And again inside run.command, which records the whole command
line as it was typed.

The second was a side effect. run.command is the line people copy: into a
bug report, into a README, into a commit beside a set of fixtures. It
reads like metadata rather than like fixture data, so it is not treated
with the same care, and the two places were one accident apart. It now
reads --set password=***, in both flag shapes the flag package takes.

Which properties are credentials is declared where the property is
declared, so a second one added later does not have to find the places
that care. internal/manifest keeps its own copy of the names and a guard
compares the two - that package records what a run produced and knows
nothing about formats, and asking the registry from there would answer
"no secrets" quietly in a process that registered none.

A manifest carrying one is written 0600 rather than 0644. Every other
manifest and every generated file keeps the mode it had: this tool exists
to produce files somebody else's CI will read, which is why .golangci.yml
turns gosec's permission rules off here. Windows has no permission bits,
so nothing changes there - and that is why the guard for it is proven by
hand on Linux and recorded in provenByProbe rather than by the mutation
runner, which runs on Windows and would score NOT CAUGHT about a healthy
guard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@donislawdev
donislawdev merged commit d58b557 into main Sep 6, 2026
18 checks passed
@donislawdev
donislawdev deleted the security/secret-in-manifest branch September 6, 2026 16:20
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.

1 participant