Cloud security engineers reviewing a CloudFormation stack where a NoEcho parameter is masked but its value remains visible in Outputs.

Your CloudFormation Parameter Is Masked. Its Output Is Not.

Tue, Oct 13, 2026

An authorized reviewer opens a completed stack description. Under Parameters, the value is a row of asterisks. Under Outputs, that same response can contain the original value, both alone and inside a longer string. The first field looks reassuring; the second answers the disclosure question. AWS’s documented NoEcho exclusions explicitly include Outputs. This is an established boundary, not a newly released feature or an encryption failure.

The review contract here is narrow: the stack’s published output values must not contain the supplied synthetic parameter, directly or embedded in another string. The reader is an authorized DescribeStacks caller, not an anonymous internet visitor. The lab defines only DEMO-NOECHO-NOT-A-CREDENTIAL, three separately named stacks, and one empty bucket per stack, with public access blocked and default encryption configured. No real secret or application bucket belongs in the exercise.

Case A leaves the parameter unmasked and publishes two echoes. Case B enables masking but retains those outputs. Case C keeps masking and removes both echoes. The expected comparison separates parameter presentation from output acceptance while binding each response to its creation receipt.

The supplied execution record covers local preparation only: syntax checking and input generation. No AWS stack was created, no service response was captured, and no live checker PASS was observed. The following outcomes are expectations to reproduce, not claimed measurements. Acceptance requires evidence from an actual qualifying run.

Define the disclosure contract before looking at masked fields

Start with a sentence a reviewer can evaluate: “For this recorded stack and completed creation, the only permitted output is SafeLabel=synthetic-noecho-lab; the supplied parameter must not appear in any output value.” That statement defines both an allowlist and a prohibited disclosure. It does not depend on whether a console chooses to hide a column.

Keep four questions separate. Display masking concerns what a particular field shows. Storage concerns where a value remains retained. Authorization concerns who may call an interface. Output publication concerns what that interface deliberately returns. A satisfactory answer to one is not a substitute for the others.

The DescribeStacks API reference describes a request scoped by stack name or identifier and a response containing stack structures. This lab always supplies the recorded identifier rather than requesting every stack. Its permitted reader is the operator already authorized to inspect that specific disposable resource.

Under this proposed application policy, an authorized reader receiving the sentinel through Outputs is still an output-contract violation. Authorization to inspect infrastructure does not change the deliberately stated rule about what the template should publish. Conversely, observing the synthetic string does not establish unauthorized access, credential compromise, or public internet exposure.

Refonte’s introduction to cloud security foundations supplies broader context; the approval question here remains one response surface and one input.

The input domain is intentionally small: one String parameter, one fixed non-secret value, and three known output designs. There is no arbitrary credential scanner. A successful result cannot certify account-wide secret handling, historical retention, or every path through a deployment toolchain. Name the accepted interface and evidence before choosing a decision label. Otherwise, “masked” can quietly become a claim much broader than the observation supports.

Read the scope of NoEcho, not just its reassuring name

The CloudFormation Parameters documentation says NoEcho=true masks parameter values with asterisks in calls describing stacks or stack events, subject to explicit exclusions. Outputs are one of those exclusions. The separate Outputs documentation states that output information is not redacted or obfuscated and advises against including passwords or secrets there.

A parameter reference creates another representation. AWS documents that Ref applied to a parameter returns that parameter’s value. In this fixture, RawEcho deliberately references DemoValue; it does not reference the asterisks shown in a response’s parameter display.

The engineering implication is straightforward: a template may be syntactically acceptable and behave as documented while violating the application’s disclosure policy. Treating that outcome as “broken encryption” misidentifies both the mechanism and the repair. The proposed remedy changes what the template publishes.

The related discussion of Terraform display masking and retained state addresses a different retained-artifact boundary. Do not transfer its implementation rules into CloudFormation. Here, the evidence is a completed stack description, not Terraform state or a saved plan.

Separate documented exclusions from executed cases

AWS’s documented NoEcho exclusions cover template Metadata and resource Metadata, and its parameter guidance cautions against sensitive values in properties forming primary resource identifiers. The resource Metadata reference separately warns that this information is not redacted.

These are documented limitations, not extra experimental cases. The templates below contain neither Metadata experiment nor sensitive identifier. DemoValue never supplies a bucket name. The lab does not inspect every metadata retrieval operation, exercise every intrinsic function, or trace all downstream destinations.

Preserve that distinction in the review record: documentation establishes the wider caution; the proposed A/B/C comparison targets Outputs alone. Even a fully successful run would not convert these excluded surfaces into tested evidence.

Prepare an isolated account and three identifiable stacks

Use one disposable AWS account and one explicitly selected region, with the account owner’s permission. Before deployment, deliberately supply AWS_PROFILE, AWS_REGION, and EXPECTED_ACCOUNT_ID. A familiar profile name is not evidence of the account actually selected. The operator sequence obtains the caller identity and compares its Account field with the independently supplied expected account.

AWS’s get-caller-identity reference identifies the returned Account, Arn, and UserId fields. Preserve that response as identity evidence; do not mistake it for permission to create or delete the lab resources.

Arrange approved permissions for stack creation, description, and deletion, and for provisioning and removing the empty bucket with its declared settings. Account policies may require additional organizational review. Costs, quotas, and policy compatibility remain the operator’s responsibility; this exercise is not represented as free.

Each template declares one AWS::S3::Bucket, enables all four public-access-block settings, and specifies AES256 default server-side encryption. It deliberately omits BucketName; the bucket resource reference documents generated naming when no name is supplied. Nothing uploads an object or places the sentinel in a resource identifier.

Prepare a writable, access-controlled evidence directory. Record the AWS CLI v2 version actually executed, the Python interpreter used for preparation and checking, the explicit region, and the caller identity. A documentation page’s version banner is not a runtime measurement, so no CLI patch version is asserted here.

Use three distinct stack names, retaining A, B, and C as case labels. Preserve the StackId returned for each creation and use it for subsequent reads and cleanup. The timestamp-and-process suffix in the commands aids naming; it is not the identity oracle.

This is repeatable infrastructure automation applied to a bounded review, not a provisioning tutorial. Keep inputs and receipts together, but do not substitute filenames for verified identities. A file called C-describe.json must still prove that it describes the stack created for C.

Create the templates and capture unfiltered stack responses

Save this complete driver as noecho_fixture.py in a writable disposable directory. It prepares the three templates and parameter file, or checks separately captured responses. Preparation itself makes no AWS call.

"""Prepare synthetic-only templates or check separately captured AWS responses."""
import hashlib
import json
import re
import sys
import tempfile
from pathlib import Path

SENTINEL = 'DEMO-NOECHO-NOT-A-CREDENTIAL'

def require(ok, message):
    if not ok:
        raise ValueError(message)

def template(case):
    require(case in 'ABC' and len(case) == 1, 'Unknown case')
    outputs = {'SafeLabel': {'Value': 'synthetic-noecho-lab'}}
    if case in 'AB':
        outputs.update(RawEcho={'Value': {'Ref': 'DemoValue'}},
                       DerivedEcho={'Value': {'Fn::Join': ['::', ['prefix', {'Ref': 'DemoValue'}, 'suffix']]}})
    return {
        'AWSTemplateFormatVersion': '2010-09-09',
        'Description': 'Synthetic-only NoEcho output qualification',
        'Parameters': {'DemoValue': {'Type': 'String', 'NoEcho': case != 'A'}},
        'Resources': {'EmptyBucket': {'Type': 'AWS::S3::Bucket', 'Properties': {
            'PublicAccessBlockConfiguration': {'BlockPublicAcls': True, 'IgnorePublicAcls': True,
                                              'BlockPublicPolicy': True, 'RestrictPublicBuckets': True},
            'BucketEncryption': {'ServerSideEncryptionConfiguration': [
                {'ServerSideEncryptionByDefault': {'SSEAlgorithm': 'AES256'}}]}}}},
        'Outputs': outputs,
    }

def check(case, describe_path, create_path):
    require(case in 'ABC' and len(case) == 1, 'Unknown case')
    raw = Path(describe_path).read_bytes()
    data = json.loads(raw)
    expected_id = json.loads(Path(create_path).read_text())['StackId']
    require(not data.get('NextToken'), 'Incomplete response')
    require(len(data.get('Stacks', [])) == 1, 'Expected exactly one recorded stack')
    stack = data['Stacks'][0]
    require(stack.get('StackId') == expected_id, 'Wrong stack identity')
    require(stack.get('StackStatus') == 'CREATE_COMPLETE', 'Stack is not complete')
    params = stack.get('Parameters', [])
    require(len(params) == 1 and params[0]['ParameterKey'] == 'DemoValue', 'Parameter inventory mismatch')
    value = params[0].get('ParameterValue', '')
    masked = bool(re.fullmatch(r'\*+', value))
    require(value == SENTINEL if case == 'A' else masked, 'Unexpected parameter display')
    pairs = stack.get('Outputs', [])
    output = {p['OutputKey']: p['OutputValue'] for p in pairs}
    require(len(output) == len(pairs), 'Duplicate output key')
    expected = {'SafeLabel': 'synthetic-noecho-lab'}
    if case in 'AB':
        expected.update(RawEcho=SENTINEL, DerivedEcho='prefix::'+SENTINEL+'::suffix')
    require(output == expected, 'Output manifest mismatch')
    return {'case': case, 'diagnostic': 'PASS', 'stack_id': expected_id,
            'parameter_masked': masked, 'output_keys': sorted(output),
            'sentinel_in_outputs': any(SENTINEL in v for v in output.values()),
            'decision': 'REPAIR_OUTPUT_DISCLOSURE' if case in 'AB' else 'ACCEPT_BOUNDED_OUTPUT_CONTRACT',
            'describe_sha256': hashlib.sha256(raw).hexdigest()}

if name == '__main__':
    if sys.argv[1:] == ['prepare']:
        dest = Path(tempfile.mkdtemp(prefix='noecho-inputs-', dir=Path(__file__).parent))
        for case in 'ABC':
            (dest/(case+'.json')).write_text(json.dumps(template(case), indent=2)+'\n')
        (dest/'parameters.json').write_text(json.dumps([
            {'ParameterKey': 'DemoValue', 'ParameterValue': SENTINEL}], indent=2)+'\n')
        print(dest)
    elif len(sys.argv) == 5 and sys.argv[1] == 'check':
        print(json.dumps(check(sys.argv[2], sys.argv[3], sys.argv[4]), indent=2))
    else:
        raise SystemExit('Use: prepare OR check CASE DESCRIBE_PATH CREATE_PATH')

The supplied preparation record reports successful syntax checking and generation of A/B/C templates plus parameters.json. Its reported source SHA-256 is 270a2a9b2ecf784b92e7d9a94a91de8fe122988fbc13d1e14dd43fc3facb11a1. That fingerprint is not a response hash, and this local activity did not validate the checker against AWS.

Run the preflight portion first and inspect its results before entering the creation loop. Confirm that aws-version.txt identifies CLI v2, record python --version, and retain the explicit region in the run record. Record SHA-256 digests of the exact generated template files and parameters.json before submitting them. Keep those inputs unchanged until response collection finishes; an input hash calculated only after an unexplained edit cannot identify what was submitted.

The paired StackId connects a creation receipt with a description, but it does not independently identify the submitted template bytes. That connection also depends on the preserved command and input inventory. In particular, C’s masked parameter display cannot independently prove which literal was supplied. The unchanged parameters file and recorded creation command establish the intended input provenance; the checker qualifies the resulting response fields.

Keep the stages distinct in the run record: input preparation, identity preflight, creation request, completion wait, description capture, and response check. A failure at any stage leaves later stages unqualified. Do not merge partial attempts into one apparently continuous success. A subsequent attempt needs its own recorded inputs, identities, command results, and captures, with the earlier failure still visible to reviewers.

The following is a proposed AWS execution. Supply the three environment values deliberately before running it in Bash, and ensure python invokes Python 3. The sequence exports all three settings so the account guard can read EXPECTED_ACCOUNT_ID. It rejects a malformed account ID or a mismatch with explicit SystemExit checks rather than assert, because Python can omit assertions under optimization.

set -euo pipefail
: "${AWS_PROFILE:?Choose the disposable AWS profile}"
: "${AWS_REGION:?Choose one explicit AWS region}"
: "${EXPECTED_ACCOUNT_ID:?Supply the authorized 12-digit account ID}"
export AWS_PROFILE AWS_REGION EXPECTED_ACCOUNT_ID AWS_PAGER=""
FIXTURE="$(pwd)/noecho_fixture.py"
LAB="$(python "$FIXTURE" prepare)"
cd "$LAB"
set -C  # Do not silently overwrite previous capture files.
aws --version > aws-version.txt 2>&1
aws sts get-caller-identity --output json > caller.json
python - <<'PYACCOUNT'
import json
import os
import re
from pathlib import Path

expected = os.environ["EXPECTED_ACCOUNT_ID"]
if re.fullmatch(r"[0-9]{12}", expected) is None:
    raise SystemExit("EXPECTED_ACCOUNT_ID must contain 12 digits")
account = json.loads(Path("caller.json").read_text())["Account"]
if account != expected:
    raise SystemExit("Wrong account")
PYACCOUNT
RUN_ID="lab$(date -u +%Y%m%dT%H%M%SZ)-$$"
for CASE in A B C; do
  NAME="refonte-noecho-${RUN_ID}-${CASE}"
  aws cloudformation create-stack --stack-name "$NAME" \
    --template-body "file://${CASE}.json" --parameters file://parameters.json \
    --output json > "${CASE}-create.json"
  ID="$(python -c 'import json,sys; print(json.load(open(sys.argv[1]))["StackId"])' "${CASE}-create.json")"
  aws cloudformation wait stack-create-complete --stack-name "$ID"
  aws cloudformation describe-stacks --stack-name "$ID" --output json \
    > "${CASE}-describe.json"
  python "$FIXTURE" check "$CASE" "${CASE}-describe.json" "${CASE}-create.json" \
    > "${CASE}-check.json"
done

Retain the complete JSON written by the CLI, not a selected column. The describe-stacks command supports JSON output and is paginated; the sequence adds neither a filtering query nor a pagination limit. Here, “raw response” means the unfiltered CLI JSON capture, not an HTTP wire capture.

Before deployment, arrange a run record retaining command errors and exit statuses. The displayed sequence saves successful response bodies but does not automatically archive every stderr message or create a failure ledger. Record the explicit region and Python version alongside it. Preserve existing captures when investigating; do not silently overwrite a failed attempt with a later success.

Treat any nonzero CLI result, waiter failure, or checker exception as HOLD. Stop qualification and inspect receipts already produced. Redirection can leave an empty file when a command fails, so file existence is not evidence of success. The checker does not emit a structured HOLD receipt on exceptions; the operator must record that disposition and the original error.

Cleanup is a separate, deliberate operation after inspection. For each nonempty A/B/C creation receipt that actually contains a StackId from this run, extract that exact identifier as above, then execute:

aws cloudformation delete-stack --stack-name "$ID"
aws cloudformation wait stack-delete-complete --stack-name "$ID"

Record both results for every recorded stack. Keep the buckets empty. Investigate failed creation and deletion rather than targeting unrelated resources. No deployment, deletion, or diagnostic PASS is claimed for the supplied preparation.

Keep creation receipts separate from completed-state evidence

The CreateStack API returns a StackId identifying the requested stack; it is not the completed state under review. The creation waiter waits for CREATE_COMPLETE. The checker independently requires that status in the subsequently captured response.

It also requires exactly one stack, a StackId matching the paired receipt, and no remaining NextToken. These checks prevent several incomplete-evidence mistakes, but they do not authenticate arbitrary local files. Preserve the authorized capture process as well as the JSON. Hashing the saved bytes identifies what was checked; it does not prove who supplied those bytes.

Establish the unmasked positive control

Case A defines NoEcho=false. Its expected parameter value is the literal DEMO-NOECHO-NOT-A-CREDENTIAL. Its output inventory contains exactly SafeLabel, RawEcho, and DerivedEcho; their expected values are the harmless label, the sentinel, and the wrapped sentinel respectively.

This positive control asks whether the declared input and echo expressions are visible in the intended response. Before examining the strings, pair A-describe.json with A-create.json. The checker must find one stack, the same StackId in both files, and CREATE_COMPLETE. Those requirements are part of the experiment, not optional bookkeeping.

Then reconcile the input itself. There must be one recorded parameter with ParameterKey=DemoValue; an unrelated parameter, missing parameter, or extra parameter fails the inventory requirement. For A, asterisks would not satisfy the expectation. Neither would a shortened sentinel, a whitespace-modified value, or an empty string. Do not normalize a mismatch into success.

The output comparison is equally exact. RawEcho must equal the sentinel, while DerivedEcho must equal prefix::DEMO-NOECHO-NOT-A-CREDENTIAL::suffix. SafeLabel must remain synthetic-noecho-lab. Merely finding one familiar string somewhere in a response is weaker than matching this complete manifest.

A qualifying future A response would establish that the positive control behaved as specified. It would not make A an acceptable application design. The checker’s proposed summary deliberately combines diagnostic=PASS with decision=REPAIR_OUTPUT_DISCLOSURE. The first classifies agreement with the control; the second classifies the design against the output policy.

That distinction also explains why the shell loop may continue after a diagnostically successful A check. The checker exits successfully when it observes the expected exposure; it is not a release-approval executable whose zero status means every design is safe. A downstream reviewer must read the decision, not simply count successful processes.

A does not establish that every possible supplied string will behave identically. Its role is narrower: qualify the fixed input and the fixed expressions before interpreting masking in B. No A service response is supplied here, so its actual status, output values, and response hash remain unrecorded rather than assumed.

Compare the masked parameter with the raw output

Case B changes NoEcho to true while leaving both echo expressions intact. The expected parameter display is a nonempty string consisting entirely of asterisks. The checker uses re.fullmatch(r'\*+', value) rather than assuming a particular mask length. An empty string does not qualify as masking.

Keep the expected parameter and outputs on the same review row:

Case

Expected DemoValue display

Expected RawEcho

Expected DerivedEcho

Design decision on agreement

B

Asterisks only

DEMO-NOECHO-NOT-A-CREDENTIAL

prefix::DEMO-NOECHO-NOT-A-CREDENTIAL::suffix

REPAIR_OUTPUT_DISCLOSURE

This is the decisive counterexample to the proposed shortcut “the parameter is masked, therefore the output is safe.” Within the specified B expectation, masking and disclosure coexist. The reviewer should be able to point to both fields in the same captured JSON, not combine a masked parameter screenshot from one stack with output evidence from another.

Compared with A, B isolates the effect of the template’s NoEcho setting. The sentinel, parameter name, resource declaration, and output expressions remain unchanged. Separate stack creation gives each case its own receipt, so the comparison must preserve both identities rather than pretend the cases are successive snapshots of one stack.

Inspect the response surface rather than a filtered console view

The oracle converts the Outputs entries into a key/value mapping, checks for duplicate output keys, and compares the entire mapping with its independently stated expectation. That comparison rejects an extra key even when the familiar keys look correct. It also rejects a missing SafeLabel, not merely missing echoes. The mapping comparison covers OutputKey and OutputValue; it is not a scan of every other field in a stack response. Preserve the approved template bytes alongside this bounded check.

An omitted CLI column is not an absent service field. A permissions error, inaccessible stack, or failed JSON capture is not redaction evidence either. Preserve the original file before producing a friendlier table; an attractive summary should help reviewers navigate evidence, not replace it.

No sensitive production value should be copied into a report to imitate this exercise. The sentinel is intentionally safe to discuss. The lesson is not to print secrets more carefully; it is to stop using parameter presentation as a substitute for reviewing the output contract. B still needs repair even when its masking diagnostic behaves exactly as expected.

Test the derived string as well as the direct reference

DerivedEcho is not decorative. It tests a second, precisely declared representation of the same parameter: the sentinel embedded between fixed prefix and suffix fragments. AWS documents Fn::Join as combining fragments with the chosen delimiter between them; Ref is supported within its value list.

For this template, the independently authored expected result is:

prefix::DEMO-NOECHO-NOT-A-CREDENTIAL::suffix

The expression has three fragments and two :: separators. There is no leading delimiter, trailing delimiter, added space, or alternate capitalization. These are properties of the supplied fixture, not measurements from a live deployment. The checker constructs the expectation directly from the known sentinel and wrapper, rather than deriving success from whatever string the response happens to contain.

A check limited to value == SENTINEL would match RawEcho and miss DerivedEcho. The wrapped value is not equal to the sentinel, but it contains the sentinel unchanged. Therefore, direct equality to a prohibited value is not sufficient for this explicitly declared embedded-value policy.

The fixture uses two complementary mechanisms. Its acceptance oracle compares every output key and exact expected value. Its summary field, sentinel_in_outputs, reports whether the sentinel occurs within any output value. The summary is descriptive; it does not replace the exact manifest requirement that runs before a PASS can be returned.

For example, a hypothetical response containing the right sentinel but a different prefix would still satisfy a substring test. It would fail this fixture’s exact comparison. That mismatch matters because it could indicate changed inputs, changed template logic, or mixed evidence. Do not waive the discrepancy simply because the response still illustrates disclosure.

Conversely, substring scanning is not a complete secret detector. This experiment does not test encodings, splitting across fields, transformations, resource attributes, or arbitrary information flows. Adding an improvised catalogue of transformations would change the experiment and require another oracle.

The bounded finding to seek is specific: in A and B, the direct and joined outputs should retain their independently expected strings. In C, neither output belongs in the inventory. This paired check is enough to test the declared publication mistake without pretending to solve general secret discovery. Preserve both comparisons in the evidence ledger rather than reducing them to a single “secret found” flag.

Remove the exposing output contract and recheck

Case C keeps NoEcho=true and removes RawEcho and DerivedEcho. Relative to B, only the Outputs contract changes. The parameter definition and empty-bucket resource remain the same. The expected remaining mapping is exactly SafeLabel=synthetic-noecho-lab.

This is the candidate repair: stop publishing the parameter instead of relying on its masked representation elsewhere. C must still present a masked DemoValue, and its description must match C’s recorded creation StackId with CREATE_COMPLETE. Matching the safe label in any convenient JSON file is not enough.

C is a separately created stack, not an update to B. A future successful C test would qualify this repaired template variant in that new stack. It would not demonstrate an in-place migration, preserve an existing application’s dependencies, or withdraw A and B’s outputs. Those exposing controls require their own evidence-preserving cleanup; creating C does not change them.

The exact-key requirement makes the candidate repair more demanding than deleting only the obvious RawEcho. Leaving DerivedEcho would fail. Renaming an echo would also fail, because the new key would not belong to the approved mapping. Removing every output would fail too: the contract requires the safe output to remain available, not merely the absence of a familiar secret-shaped string.

Distinguish an absent output from an absent result

An empty file cannot demonstrate C. Neither can a response with no stacks, multiple stacks, a remaining NextToken, the wrong StackId, a missing parameter, or a nonmatching status. The checker requires positive evidence that the intended completed result was actually inspected before accepting the reduced output set.

AWS’s Outputs guide notes that values are available after stack operations complete, not while the stack is in an IN_PROGRESS status. The lab’s stricter requirement is specifically CREATE_COMPLETE, matching its fresh-creation workflow rather than accepting any vaguely finished state.

Do not interpret an output absent during an incomplete operation as successful removal. Waiter failure must lead to investigation, not an early snapshot labelled C.

The repair claim also has a time boundary. Removing output definitions from a candidate design says nothing about older responses already saved by authorized readers. This exercise neither erases those copies nor proves they never existed. In the supplied record, even the narrower live C acceptance remains unexecuted.

Reject attractive checks that answer the wrong question

Several checks are useful in deployment work yet insufficient for this review. Their problem is not that they are worthless; it is that their evidence has a different meaning.

Attractive check

What it does not establish here

Template validation succeeds

The proposed output values comply with the disclosure policy

Creation returns a StackId

The stack reached the completed state being reviewed

DemoValue displays asterisks

No other response field contains the supplied value

RawEcho is absent

The joined echo or an unexpected output is absent

A console screenshot looks clean

The saved, unfiltered JSON matches the full manifest

The bucket declares encryption

CloudFormation output strings omit the sentinel

Treat the table as a separation of review questions, not a demand for a larger toolchain. This experiment needs the supplied templates, authorized CLI captures, paired receipts, and exact checker. A generic scanner score would add another measurement without replacing any of those requirements.

The bucket settings are deliberately benign fixture controls. AWS describes BucketEncryption as default bucket encryption configuration; that resource property is not the output-redaction contract. In this template, the sentinel flows from DemoValue into Outputs, not into S3 object contents. The resource declaration and the response-field review therefore address different paths.

Similarly, Refonte’s discussion of secret delegation in reusable workflows concerns delegated CI authority. It should not become a detour into workflow inheritance, token permissions, or repository events. The present question is what one authorized stack-description consumer receives.

Adding NoEcho while retaining the echoes produces B, not C. In the specified expectation, that edit changes parameter presentation while leaving the policy-violating outputs intact. Calling it “the secret fix” would approve the wrong variant.

Nor should a reviewer search only for the sentinel in a filtered report. The unfiltered source must first satisfy identity, completion, and inventory checks. Only then can a compact report faithfully summarize the relevant result. A clean presentation built from incomplete evidence is a HOLD condition, not a substitute repair.

Reconcile the complete three-case evidence ledger

Keep one ledger covering every required case. The following is a collection plan and expected matrix, not a populated execution report. “Exact sentinel” and “exact wrapper” refer to the complete strings already specified, without normalization.

Evidence field

A

B

C

Template SHA-256

Record hash of A.json

Record hash of B.json

Record hash of C.json

Creation StackId

Capture from A-create.json

Capture from B-create.json

Capture from C-create.json

Response SHA-256

Hash A-describe.json

Hash B-describe.json

Hash C-describe.json

Required final status

CREATE_COMPLETE

CREATE_COMPLETE

CREATE_COMPLETE

Expected parameter display

Exact sentinel

Asterisks only

Asterisks only

Expected output keys

SafeLabel, RawEcho, DerivedEcho

SafeLabel, RawEcho, DerivedEcho

SafeLabel only

RawEcho comparison

Exact sentinel

Exact sentinel

Key absent

DerivedEcho comparison

Exact wrapper

Exact wrapper

Key absent

Decision after qualifying comparison

REPAIR_OUTPUT_DISCLOSURE

REPAIR_OUTPUT_DISCLOSURE

ACCEPT_BOUNDED_OUTPUT_CONTRACT

For every case, SafeLabel must equal synthetic-noecho-lab. Preserve the source files, parameters file, caller record, commands, receipts, raw descriptions, and checker summaries together. The checker produces the response digest; template digests require a separate input-inventory step before deployment.

The digest covers saved response bytes. Reformatting JSON changes those bytes, so preserve the original capture rather than replacing it with a prettified equivalent. The summary intentionally omits raw parameter and output values. That makes it convenient to review but insufficient for independently checking the exact comparisons without the underlying evidence.

Classify failed checks without inventing AWS observations

Separate failures by stage. A permission failure means the requested operation was not successfully evidenced; retain the actual AWS diagnostic. A waiter failure leaves completion unqualified. Wrong stack identity and Output manifest mismatch are distinct ValueError messages from the checker, requiring different investigations.

Malformed JSON may produce JSONDecodeError; missing required structure can produce KeyError or another parsing/structure failure. None should be relabelled as a successful absence test. Record the observed exception and command status on an actual run, without inventing a service cause from the exception alone.

An unexpected output could reflect a changed template, incorrect input, mixed capture, or unexplained service result. Preserve the contradiction until resolved. Do not edit the response, loosen the expectation after seeing it, or drop the failed case from the ledger. The current supplied record contains no AWS rows to reconcile; all three remain awaiting actual captures.

Recover an exposed interface without promising historical erasure

A real sensitive-output discovery calls for a response owned by the relevant system and credential owners. Do not repeat the synthetic procedure with a real password merely to produce a convincing screenshot. Preserve necessary evidence in an approved restricted location and avoid spreading the value into tickets, chat messages, or broadly accessible attachments.

As an operational design, separate current-interface repair from historical exposure assessment. Removing a sensitive output addresses a publication path in the changed template. It cannot recall a response already received and saved by an authorized consumer. A clean new response therefore cannot establish that no earlier copy exists.

The credential owner must determine whether the exposed value remains usable and whether replacement or revocation is required. The system owner must consider affected consumers and the scope of any access review. Those are incident-specific decisions, not outcomes demonstrated by a non-secret sentinel.

The evidence question is also different from the value question. A reviewer may need to retain enough information to establish which interface exposed which credential without duplicating the credential into every working document. Use the organization’s incident-handling procedures to decide what evidence is necessary, who may access it, and how its retention is governed.

Do not automatically erase the diagnostic record in the name of cleanup. Resource deletion, evidence retention, and credential invalidation are separate actions with separate owners. Likewise, repairing an output should not silently close unresolved questions about prior readers or downstream copies.

This article performs no real credential rotation, forensic log review, or incident-response certification. It supplies a bounded template comparison and a proposed acceptance gate. For an actual incident, record the interface repair and the remaining investigation separately. A verified current output contract may be one useful recovery artifact; it is not an all-clear for the earlier exposure or for every place a sensitive value might have travelled.

Choose accept, repair or hold from the declared evidence

Use the decision names as technical workflow outcomes, not general security endorsements. Their meaning depends on the case, the evidence source, and the policy already declared.

For A and B, diagnostic agreement leads to REPAIR_OUTPUT_DISCLOSURE. The response matches the deliberately exposing control, but its design violates the permitted output mapping. Do not replace that decision with ACCEPT because AWS behaved according to documentation or because stack creation succeeded.

For C, ACCEPT_BOUNDED_OUTPUT_CONTRACT requires the actual receipt-paired response, CREATE_COMPLETE, the required masked parameter inventory, and exactly the safe output mapping. The acceptance statement should name C’s StackId and response digest. It applies to that response under this input and output contract.

HOLD covers missing or incomplete captures, missing required cases, wrong identities, unauthorized source material, and unexplained contradictions. A source file’s reassuring name cannot override those gaps. Neither can a previously accepted C response be reused as evidence for a newly created stack with a different receipt.

Keep authorization provenance outside the checker’s promise. The code trusts its input files enough to parse and compare them; it does not verify AWS signatures or independently establish who captured them. The operator’s authorized collection record is a required part of the proposed workflow.

Keep a passed negative control out of the release approval

A and B are exposure controls. If an actual run agrees with the expected outputs, they provide positive evidence for the diagnostic harness and negative examples for the release policy. Count these categories separately. In a fully qualifying future comparison, three diagnostic checks could agree while only one template variant meets the final output contract.

The supplied work completed local preparation only. It contains no service-side acceptance decision and no live checker PASS. That limitation does not prevent explaining the documented boundary; it prevents calling the proposed deployment results measured facts.

Even an executed C would not certify the whole template, account, deployment toolchain, every service log, or all historical captures. Preserve the word BOUNDED: it identifies exactly what was accepted and leaves untested surfaces unclaimed.

Assign output ownership and clean up recorded resources

Give the template owner responsibility for the output schema and its consumers. Give the security reviewer responsibility for defining prohibited sensitive-value flows and the evidence needed for acceptance. Give the operator responsibility for authorized execution, receipt preservation, response identity, and resource cleanup.

That division makes cloud-native security review concrete without turning this lab into a general delivery programme. Someone must own both the decision to remove an output and the evidence showing which deployed variant was checked.

Delete only stacks identified by valid creation receipts from this run. The DeleteStack API accepts the stack name or unique identifier; a successful request is not the completed deletion. Follow each request with the deletion waiter and retain both command results.

A qualification failure does not cancel cleanup responsibility. Inspect any StackId already returned, including one associated with an unsuccessful creation. Do not assume the absence of a PASS means nothing needs attention.

Keep every bucket empty throughout. AWS’s bucket resource reference states that buckets with contents cannot be deleted through this resource’s normal deletion path. Unexpected contents require investigation, not an improvised purge of unrelated data.

Avoid wildcard deletion, guessed identifiers, and cleanup based only on a familiar name prefix. Record unresolved deletion failures for the account owner rather than abandoning resources. Finally, retain the appropriately protected evidence record after resource cleanup so that the technical decision remains reviewable.

Practise cloud-security review with a verified learning path

The practical habit is to inspect what authorized consumers actually receive, not just the field that looks masked. Define the permitted output schema, preserve the stack identity, compare exact values, and stop when the evidence cannot support the decision.

For this fixture, that means distinguishing A and B’s expected disclosures from C’s candidate repair, then requiring a real completed response before accepting C. Neither a reassuring screenshot nor a local preparation check can replace that evidence.

For broader foundations, explore Refonte Learning’s Cloud Security Engineer Essentials. Its programme page lists three months at 10–12 hours per week, with subjects including IAM, encryption, cloud monitoring and logging, incident response, governance, and secure development. Those subjects provide relevant context for reasoning about access, evidence, and publication boundaries.

The programme page does not establish that this exact CloudFormation lab is taught. Treat the exercise as a specific review method, not a course or employment promise. The bounded action remains the same: remove the exposing outputs, then approve only the completed, identity-matched response that satisfies the declared contract.