---
title: 'Sensitive Values'
source: 'https://academia.sh/en/courses/infrastructure-as-code/sensitive-values'
course: 'Infrastructure as Code'
language: en
updated: '2026-09-13T05:08:25+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Counts how many copies a sensitive value leaves, at how many trace points, from the definition to the state file'
    - 'Shows the limit of marking with two numbers: how many points closed, how many stayed open'
    - 'Measures where the old value stays after rotation, and the exposure that remains'
---

# Sensitive Values

A fictional password is passed through a definition, and a trace scanner counts how many copies it leaves at seven trace points; then fields are marked sensitive and the scan runs again. The points marking closes and cannot close, the residue left after rotation, and exposure are each measured separately.

The previous lesson counted the inputs of text pulled from the module registry, and all of them
were harmless — a network block, a port number, a queue name. In this lesson, one of the inputs is
a data store password. The value did not change, the path did not change, what got measured
changed — the question asked is no longer "how many lines," it is **"how many places does it show
up."**

A **secret** is the value itself that has to be kept. A **sensitive value** is the mark that value
carries throughout the record; a field stamped "do not display this." The gap between them is what
this lesson measures: the mark is placed on the **field**, not the value, and the moment a value is
copied from one field to another, the mark does not travel with it.

Secret leakage was measured twice in this module's earlier courses: one ran a repo-wide leak scan
and dealt with encrypted secret storage, the other counted values showing up in pipeline records.
Those scans are not repeated here. What is measured is narrower: **the trace a single value leaves
in a single apply.**

The measurement is again on the **regional measurement network**, and the network is **fictional**.
The value itself is fictional too; no real secret, key, or credential is written. No real provider
is connected to; the trace points are a **model** built with `node`.

**SM37.** The sensitive value is a fictional string: `FICTIONAL-password-7f31`.
**SM38.** The value enters from a variable file in the repo, becomes an input to the module, and
is written into two resource fields: directly into the `password` field, and into a connection
string the value is embedded in.
**SM39.** There are seven trace points: the variable file, the plan output, the apply log, the
state file, the module output, the provider error message, and the pipeline record. The pipeline
record carries a copy of the others.
**SM40.** Marking is done on the field; a marked field is masked in the plan, the log, and the
module output.
**SM41.** The state file is unencrypted and unaffected by marking; the provider error message does
not know the masking rule.
**SM42.** Three passes are run: unmarked, `password` only marked, `password` and `connection`
marked. Then the value is rotated, and how many places the old value stays is counted.
**SM43.** Exposure is a **modeled number**: copies × readers × retention days. The place, day, and
reader counts are written inside the lesson.

## The Value's Path and the Trace Scanner

```js
// trace.mjs — a fictional sensitive value passes through a definition, trace points are produced, a trace scanner runs.
const SECRET = "FICTIONAL-password-7f31";              // fictional string; not a real secret
const NEW = "FICTIONAL-password-b204";                 // fictional value after rotation

const generate = (d, marked) => {                       // marked: marked fields; masked in display
  const fields = { password: d, connection: `measurement:${d}@store.measurement-network:5432` };
  const mask = (a) => (marked.has(a) ? "(sensitive value)" : fields[a]);
  const plan = `+ data-store measurement-store\n    password = ${mask("password")}\n` +
    `+ connection measurement-connection\n    string = ${mask("connection")}`;
  const log = `applied data-store measurement-store password=${mask("password")}\n` +
    `applied connection measurement-connection string=${mask("connection")}`;
  const state = JSON.stringify({ "data-store": { password: fields.password },   // state is unencrypted
    connection: { string: fields.connection } });                               // marking does not affect it
  const output = `output connection-string = ${mask("connection")}`;
  const error = `provider error 400: invalid request, body {"password":"${fields.password}"}`;
  return { values: `password = ${fields.password}`, plan, log, state, output, error,
    pipeline: [plan, log, output].join("\n") };          // the pipeline record carries a copy of the others
};
const scan = (trace, d) => Object.entries(trace).map(([a, m]) => [a, m.split(d).length - 1]);

const POINT = ["values", "plan", "log", "state", "output", "error", "pipeline"];
const PASS = [["unmarked", new Set()], ["password marked", new Set(["password"])],
  ["password+connection", new Set(["password", "connection"])]];
console.log("pass                 " + POINT.map((a) => a.padStart(10)).join("") + "   point  copies");
const results = {};
for (const [label, marked] of PASS) {
  const s = scan(generate(SECRET, marked), SECRET);
  results[label] = s;
  console.log(label.padEnd(21) + s.map(([, n]) => String(n).padStart(10)).join("") +
    `   ${s.filter(([, n]) => n).length}/${POINT.length}` +
    `${String(s.reduce((a, [, n]) => a + n, 0)).padStart(7)}`);
}
const first = results["unmarked"], last = results["password+connection"];
const closed = first.filter(([a, n], i) => n && !last[i][1]).map(([a]) => a);
const open = last.filter(([, n]) => n).map(([a]) => a);
console.log(`after marking both, closed ${closed.length}/${POINT.length} (${closed})` +
  ` | still open ${open.length}/${POINT.length} (${open})`);

const RESIDUE = { "state version -1": generate(SECRET, new Set(["password", "connection"])).state,
  "archived pipeline record": generate(SECRET, new Set()).pipeline, "repo history": generate(SECRET, new Set()).values };
const newRun = scan(generate(NEW, new Set(["password", "connection"])), SECRET);
const res = scan(RESIDUE, SECRET);
console.log(`after the value is rotated, the old value in the new run: ${newRun.reduce((a, [, n]) => a + n, 0)} copies` +
  ` | in the residue: ${res.filter(([, n]) => n).length} places, ${res.reduce((a, [, n]) => a + n, 0)} copies: ` +
  res.map(([a, n]) => `${a} ${n}`).join(", "));
console.log(`the state file carried ${first[3][1]} copies in every pass; marking never reduced it in any pass`);

const LOCATION = {                                      // trace point: [where it lives, retention days, readers]
  values: ["repo", 3650, 24], plan: ["pipeline record", 90, 31], log: ["pipeline record", 90, 31],
  state: ["remote state store", 3650, 6], output: ["other definitions", 3650, 12],
  error: ["support record", 365, 40], pipeline: ["pipeline archive", 90, 31] };
const exposure = (s) => s.reduce((a, [ad, n]) => a + n * LOCATION[ad][1] * LOCATION[ad][2], 0);
console.log("point     where               days  readers  copies(first/last)  exposure(first/last)");
for (const [i, [ad, n]] of first.entries()) {
  const [y, g, o] = LOCATION[ad];
  console.log(`${ad.padEnd(10)}${y.padEnd(20)}${String(g).padStart(4)}${String(o).padStart(8)}` +
    `${`${n}/${last[i][1]}`.padStart(16)}${`${n * g * o}/${last[i][1] * g * o}`.padStart(20)}`);
}
const totalCopies = (s) => s.reduce((a, [, n]) => a + n, 0);
console.log(`copies ${totalCopies(first)} -> ${totalCopies(last)} (${Math.round((1 - totalCopies(last) / totalCopies(first)) * 100)}% down)` +
  ` | exposure ${exposure(first)} -> ${exposure(last)} (${Math.round((1 - exposure(last) / exposure(first)) * 100)}% down)`);

const CONSUMERS = ["field-service", "reporting", "validator"];  // definitions that take in the module's output
const downstream = Object.fromEntries(CONSUMERS.map((t) =>       // each writes to its own state
  [`state:${t}`, JSON.stringify({ [t]: { connection: `measurement:${SECRET}@store.measurement-network:5432` } })]));
const down = scan(downstream, SECRET), downExposure = totalCopies(down) * LOCATION.state[1] * LOCATION.state[2];
console.log(`definitions consuming the output ${CONSUMERS.length}, each its own state file | ` +
  down.map(([ad, n]) => `${ad} ${n}`).join(" ") + ` | open points with split state ` +
  `${open.length}+${down.filter(([, n]) => n).length}=${open.length + down.length}` +
  ` | copies ${totalCopies(last)}+${totalCopies(down)}=${totalCopies(last) + totalCopies(down)}` +
  ` | exposure ${exposure(last)}+${downExposure}=${exposure(last) + downExposure} (down ` +
  `${Math.round((1 - (exposure(last) + downExposure) / exposure(first)) * 100)}% from the unmarked pass)`);
```

```
pass                     values      plan       log     state    output     error  pipeline   point  copies
unmarked                      1         2         2         2         1         1         5   7/7     14
password marked               1         1         1         2         1         1         3   7/7     10
password+connection           1         0         0         2         0         1         0   3/7      4
after marking both, closed 4/7 (plan,log,output,pipeline) | still open 3/7 (values,state,error)
after the value is rotated, the old value in the new run: 0 copies | in the residue: 3 places, 8 copies: state version -1 2, archived pipeline record 5, repo history 1
the state file carried 2 copies in every pass; marking never reduced it in any pass
point     where               days  readers  copies(first/last)  exposure(first/last)
values    repo                3650      24             1/1         87600/87600
plan      pipeline record       90      31             2/0              5580/0
log       pipeline record       90      31             2/0              5580/0
state     remote state store  3650       6             2/2         43800/43800
output    other definitions   3650      12             1/0             43800/0
error     support record       365      40             1/1         14600/14600
pipeline  pipeline archive      90      31             5/0             13950/0
copies 14 -> 4 (71% down) | exposure 214910 -> 146000 (32% down)
definitions consuming the output 3, each its own state file | state:field-service 1 state:reporting 1 state:validator 1 | open points with split state 3+3=6 | copies 4+3=7 | exposure 146000+65700=211700 (down 1% from the unmarked pass)
```

## Three Passes

In the unmarked pass, the value shows up in **seven of the seven trace points** and leaves
fourteen copies in total. Fourteen copies for a single input in a single apply. Most of that
number comes from duplication: the pipeline record collects the plan, the log, and the output as
they are, so it carries five copies on its own. None of these five copies were born from a new
decision; no one said "write the password into the pipeline record." A record was read, its output
was written somewhere, and that place was also kept. **Most copies are not a decision, they are a
byproduct**, and that is why none of them show up in a review.

The second pass shows how narrow marking really is. The `password` field was marked; the plan and
the log masked that field, and the copy count dropped from fourteen to ten — but **the visible
point count is still seven**. The reason was written in the first section: the value had been
copied from one field into another. The `connection` string is a separate field because it embeds
the password, and it was not marked. Masking recognizes the field, not the value. What marking
misses is not a rule, it is a **copy**.

The third pass marks the second field too, and that is when the real drop comes: copies fall to
four, visible points to three. The four points that close are the plan, the log, the module
output, and the pipeline record. The three points that stay open are the variable file, the state
file, and the provider error message — and they stay open for three separate reasons.

The variable file stays open because marking is a **display** rule; it has no say over where the
value is written. The mark says what will be shown where the record is read, not what is written
in the record itself. The provider error message stays open because the masking rule belongs to
the side that writes the record, and the side that produces the error does not know it; the error
writes the sent request's body back exactly as it was. What these two have in common is that both
are **outside the record's control**: one comes before the record, the other after it. The state
file is unlike either of them; it sits right in the middle of the record, which is why it matters
most.

## State, Residue and Exposure

The state file carried two copies in all three passes, and marking never reduced it in any pass.
The reason is what state is: state is the record of the applied resources' actual values.
Stamping a field "do not display" does not remove that field's **value**; the value has to stand
somewhere for the next plan to be able to compare record against reality. This course's rule holds
here too: infrastructure is only as manageable as its record, and the record has to hold the
value.

Rotation shows this once more. After the value is replaced with a new one, the old value leaves
**zero copies** in the new run — clean going forward. Not going backward: three places and eight
copies stand in the residue. Two in the old state version, five in the archived pipeline record,
one in the repo's history. Five of the eight copies come from a single archive taken while marking
did not exist yet; a mark placed today does not fix yesterday's record. Rotation invalidates the
value but **does not erase it from the record**; what now needs erasing is not the value, it is
the record itself.

The final table measures the value of marking one more time, weighted this time. Exposure
multiplies the copy count by the retention time of where it sits and the number of people who can
read that place; it is a **modeled number**, not a real measurement. The result is this: the two
markings erased **seventy-one percent of the copies** but only **thirty-two percent of the
exposure**. The gap comes from every closed point being short-lived — the plan, the log, and the
pipeline record are kept ninety days, while two of the three open points are permanent.

The distribution of remaining exposure is also different from what you would expect. The largest
share is not in the state file, it is in the repo's variable file: eighty-seven thousand six
hundred of one hundred forty-six thousand, that is sixty percent. As long as the value is written
in the repo, the twenty-four people who can read it outnumber the six who can read the remote
state store. Marking never reduces this share; what would reduce it is a different decision —
taking the value out of the record and pulling it in from outside at apply time.

## Multiplying in Split State

The count so far measured a single definition's single state. State was split earlier in this
topic: each team got its own definition, its own state, and its own apply calendar. Split state
behaves differently with a sensitive value.

The module gives an output called `connection-string`, and three downstream definitions take that
output as an input. The output was masked in display — in the third pass, the `output` point
showed zero copies. But masking is display; what goes to the consuming side is the value itself.
All three definitions write the value into their own resource, and the resource lands in their own
state file. The last line counts this: **three new state files, three new copies.**

The total therefore flips. After marking, four copies were left at three points; once split state
is counted in, that becomes seven copies at six points. Exposure is harsher still: one hundred
forty-six thousand climbs by sixty-five thousand seven hundred to two hundred eleven thousand
seven hundred. Compared against the unmarked pass's two hundred fourteen thousand nine hundred
ten, the drop is **one percent**. Two fields were marked, four trace points closed, and total
exposure barely changed at all.

The reason is not state splitting itself, it is the limit of marking. The mark is placed on one
definition's display; a value that crosses a definition boundary leaves the mark behind. A module
output is one boundary, a value one definition hands to another is another boundary, and the same
thing happens at every boundary — the value crosses, the mark does not. Splitting state made
record management easier, but as far as sensitive values go, what it did was **multiply the copy
count by the number of splits**.

## Summary

- A fictional password shows up in 7 of 7 trace points in a single apply and leaves 14 copies;
  the pipeline record alone carries 5 copies because it duplicates the plan, the log, and the
  output.
- Marking only `password` brought copies from 14 to 10, but the visible point count stayed at 7 —
  the value had been copied into a connection string; marking both fields brought copies to 4,
  points to 3, and closed the plan, the log, the module output, and the pipeline record.
- The 3 points that stay open are the variable file, the state file, and the provider error
  message; the state file carried 2 copies in all three passes, because the record has to hold
  the value to be able to compare.
- After rotation, the old value leaves 0 copies in the new run; 8 copies stand in the residue at
  3 places: the old state version, the archived pipeline record, and the repo history.
- Marking erased 71 percent of the copies but only 32 percent of the exposure; 60 percent of the
  remaining exposure is in the repo's variable file.
- When 3 definitions consuming the module output wrote the value into their own state files, open
  points went from 3 to 6, copies from 4 to 7, and the drop in exposure fell from 32 percent to 1
  percent: the value crosses the definition boundary, its mark does not.

## Next Step

Across this topic, the record was taken up four times. First the state file and the lock were
built, then state was split, then a repeated definition was extracted into a module and pulled
versioned from a shared registry, and finally the sensitive values passing through it were marked.
At every step, the record's scope, its cost, and its own risk were counted.

One thing was never done: the record was **never tested for correctness before being applied**.
Every record written in this topic was tried by applying it instead of being tested — the plan was
looked at, it was applied, the result was seen. A misspelled field, a resource that violates a
policy, or a change more expensive than expected only surfaced during or after the apply. The next
topic takes up this gap: checks that run on the infrastructure definition before the apply are
written, and this is counted — how many of a definition's defects can be caught by looking at the
text, how many are only visible once applied, and how many false alarms the catching check
produces.
