# freshness-bound-caller-time

> Caller-supplied time reaches a security-sensitive effect without a canonical block-time freshness bound.

Canonical: https://nightshade.dicksonwu.xyz/rules/freshness-bound-caller-time.html
Release: Nightshade 0.1.1 (v0.1.1)

| Field | Value |
| --- | --- |
| Rule ID | `freshness-bound-caller-time` |
| Severity | medium |
| Confidence | medium |
| Maturity | experimental |
| Selection | Audit default |
| Policy intent | not required |
| Default enabled | yes |
| Category | Audit-default ordinary rules |
| Engine facts used | resolved-model, state-writes, public-sinks |
| Release | v0.1.1 |

## Selection and scope

Selected by the default audit profile, so an ordinary scan runs it.

```sh
./.nightshade/node_modules/.bin/nightshade ./contracts --detectors freshness-bound-caller-time
./.nightshade/node_modules/.bin/nightshade detector show freshness-bound-caller-time
```

The first command runs only this rule; `--detectors` takes a comma-separated list. The second prints the rule's metadata from your installed version.

Severity is review priority. It does not express exploitability or impact. Confidence is detector metadata, not a probability. All built-in rules are experimental.

Compiler scope: the engine supports the pinned profiles 0.28.0, 0.29.0, 0.30.0, 0.31.1, 0.33.0-rc.2, 0.34.0, 0.35.0-rc.1 and 0.35.0, with ZKIR2 by default. Support for this rule can be narrower; the rule documentation below states any narrower scope.

## Verified example

Synthetic example from the proof run of the released CLI. The circuit accepts a caller-supplied time window without bounding it against chain time. Full example: https://nightshade.dicksonwu.xyz/examples.md#freshness-bound-caller-time

```compact
pragma language_version >= 0.23;
import CompactStandardLibrary;

export ledger startedAt: Uint<64>;
export ledger authenticated: Boolean;

constructor() {
  startedAt = 0;
  authenticated = false;
}

export circuit authenticate(expectedTimeWindow: Uint<64>): [] {
  assert(disclose(expectedTimeWindow >= startedAt), "window");
  startedAt = disclose(expectedTimeWindow);
  authenticated = true;
}
```

Output (exit code 1):

```text
$ nightshade vulnerable.compact --config config.json --compiler /path/to/compactc

MEDIUM — Caller time is not bound to protocol time [freshness-bound-caller-time]
vulnerable.compact:14:3
  14 |   startedAt = disclose(expectedTimeWindow);
     |   ^
authenticate accepts caller-supplied expectedTimeWindow and uses it in a state-changing or public effect without an asserted canonical block-time bound. A delayed, future-dated or stale proof may remain valid outside the intended freshness window. This source/effect review does not establish transaction ordering or exploitability.
Source: vulnerable.compact:12:29
Call path: authenticate
Remediation: Compare the caller value with block time using canonical blockTimeGte/blockTimeGt and blockTimeLte/blockTimeLt checks, or derive the deadline solely from a kernel-bound timestamp. Keep the bound on the same successful path as the state-changing effect.

Semantic profile: compact-0.34.0-zkir2.
Baseline analysis: complete; selected rules: complete.
Rule coverage: 1 selected rule(s); 0 with incomplete entries.
1 finding(s), 0 suppressed, 0 baseline; 2/2 entrypoint(s) completed; analysis complete. Compiler validation passed.
```

Corrected: Bound the caller's time with blockTimeGte before using it.

```compact
pragma language_version >= 0.23;
import CompactStandardLibrary;

export ledger startedAt: Uint<64>;
export ledger authenticated: Boolean;

constructor() {
  startedAt = 0;
  authenticated = false;
}

export circuit authenticate(expectedTimeWindow: Uint<64>): [] {
  assert(blockTimeGte(disclose(expectedTimeWindow)), "future window");
  assert(disclose(expectedTimeWindow >= startedAt), "window");
  startedAt = disclose(expectedTimeWindow);
  authenticated = true;
}
```

Output (exit code 0):

```text
$ nightshade corrected.compact --config config.json --compiler /path/to/compactc

Semantic profile: compact-0.34.0-zkir2.
Baseline analysis: complete; selected rules: complete.
Rule coverage: 1 selected rule(s); 0 with incomplete entries.
0 finding(s), 0 suppressed, 0 baseline; 2/2 entrypoint(s) completed; analysis complete. Compiler validation passed.
```

Paths are shortened to the example folder. A quiet result shows that this rule stopped firing, not that the contract is secure.

## Corpus checks

Synthetic corpus checks for this rule in the 0.1.1 proof run. A check counts only with compiler acceptance and complete analysis.

| Positive detected | Corrected quiet | Lookalike quiet | Other negative quiet | Incomplete |
| ---: | ---: | ---: | ---: | ---: |
| 12/12 | 12/12 | 12/12 | 0/0 | 0 |

No deviations were recorded for this rule.

These are behavior checks on synthetic fixtures, not a precision or recall estimate. See [evaluation](https://nightshade.dicksonwu.xyz/evaluation.md#synthetic-corpus).

## Rule documentation

From https://github.com/DicksonWu654/nightshade/blob/v0.1.1/docs/detectors/freshness-bound-caller-time.md at v0.1.1.

Availability: **default source/effect review**, no policy required. Severity/confidence: **medium/medium**.

This rule flags an exported circuit that uses caller-supplied time to enforce a
freshness or deadline condition without binding that value to a kernel timestamp
on the same successful path. A parameter name or `Uint<64>` type alone does not
establish that a value is time.

Remediation: bind the supplied time to chain time in the same successful path, or derive the gate from a kernel-bound timestamp. Review both the timestamp-recording and delayed-action circuits.

A deliberate caller-selected time source can match and is not automatically a
defect. The source/effect review does not infer time semantics from names or
comments, follow every helper-mediated constraint, or prove a feasible stale-time
execution.

## Suppress a match

If a match is intentional, suppress it on the line before with the exact rule ID and a reason of at least eight characters:

```compact
// nightshade-disable-next-line freshness-bound-caller-time -- Reason this match is intentional
```

Diagnostics cannot be suppressed, and an unmatched directive for a selected rule makes analysis incomplete. See [configuration](https://nightshade.dicksonwu.xyz/configuration.md#suppress-an-intentional-match) for fingerprint suppressions and baselines.

## References

- [Rule source document at v0.1.1](https://github.com/DicksonWu654/nightshade/blob/v0.1.1/docs/detectors/freshness-bound-caller-time.md)
- [Catalog entry in rules.json](https://nightshade.dicksonwu.xyz/rules.json)
- [Verified example](https://nightshade.dicksonwu.xyz/examples.md#freshness-bound-caller-time)
- [All rules](https://nightshade.dicksonwu.xyz/rules.md#audit-default-ordinary-rules)
- [Limitations](https://nightshade.dicksonwu.xyz/limitations.md)
