# reading a secret's value (Secret.Name)

> `Secret.Name` in a function body evaluates to the declared secret's value — the key itself, as a string, read at the moment it is used. It is how a webhook signature gets its shared key or an outbound call gets its token. The name must be one your app declares in `app.Secrets`; an undeclared name is a compile error, not a runtime null. The value never enters your source, and the compiler refuses the four ways it could escape the server — returning it, storing it, reading it in anything the browser runs, or handing it to a sink such as `Log`.

<!-- id: function-secret-read · area: function · stability: stable · html: https://osysharp.com/reference/function/secret-read/ -->

## Summary        {#summary}
`Secret.Name` reads a **declared secret's value** inside a function body. It evaluates to the stored string — an API
key, a shared signing key, a token — at the moment the line runs.

The same `Secret.Name` handle also appears in config slots such as `app.DefaultModel`'s `ApiKey`
([declaring secrets (app.Secrets)](https://osysharp.com/reference/config/secrets/)). There it *points at* a secret for the platform to resolve; in a function body it *is* the value,
because that is what code needs in order to sign, compare, or send it.

```osy syntax
var expected = Crypto.HmacSha256Hex(Secret.CarrierWebhook, payload);
```

## Signature      {#signature}
```osy syntax
Secret.<Name> -> string
```

`<Name>` is written as an identifier, exactly as declared — `Secret.CarrierWebhook` for
`new Secret("CarrierWebhook")`. It is not a string, not a lookup, and takes no arguments.

## Description    {#description}
A secret has to be declared before it can be read. `app.Secrets = [ new Secret("CarrierWebhook") ];` declares it;
`osy secret set CarrierWebhook` gives it a value on your machine ([Giving a secret its value](https://osysharp.com/reference/local/giving-a-secret-its-value/)); a deployed
app's values are supplied by whoever operates its platform.

**An undeclared name is a compile error.** That is deliberate and it is worth knowing why: a mistyped credential that
resolved to nothing would make every signature check compare against a key of empty string, which fails exactly like
a wrong signature from the sender. You would go looking at the sender, and you would keep looking. The compiler
refuses the typo instead.

**A declared secret with no value throws when it is read**, and says which secret and how to set it. This is also not
an empty string, for the same reason — an unset key is the ordinary state of a fresh checkout, and it must not
silently degrade into a check that always fails.

### It is server-side, always      {#server-side}
Reading a secret is a **server** operation. A credential has no client-side producer, so an expression containing
`Secret.Name` pins its containing code to the server ([execution side](https://osysharp.com/reference/function/execution-side/)) — it can never be evaluated in a
browser, and no component that runs there can reach one.

### What you may not do with the value      {#confinement}
The read gives you the plaintext, so the compiler governs where that plaintext may go. **Four things are compile
errors**, not warnings — a warning can be ignored and a leaked credential cannot:

| refused | why |
|---|---|
| **returning** it, from any function, method or constructor | a return value goes to the caller, and a caller can be a page action — the key would land in component state and on the screen |
| **storing** it in an entity field | a stored credential is readable by everything that can read that row, rides into backups and exports, and can no longer be rotated by `osy secret set` |
| **reading** it in anything sent to the browser — an `action`, a component `method`, a render slot | evaluating it there means the plaintext was shipped |
| **handing** it to an effect — `Log.*`, a file write, the clipboard, a dialog, a topic publish, a workflow event payload | each of those puts what it is given somewhere durable or observable. `Log` is the one that surprises people: it is **dual-sided**, so a client log line is in the visitor's own browser console as well as in `osy logs` |

```osy syntax
string SigningKey() { return Secret.CarrierWebhook; }   // ✗ returned
new AuditRow { Token = Secret.CarrierWebhook };         // ✗ stored
Log.Information(Secret.CarrierWebhook);                 // ✗ logged — and shown, on the client
```

**What stays legal is the whole point of the feature**: read the secret and *use* it, then let the RESULT travel.
Signing with it, comparing with it, and presenting it to the service it authenticates to are all ordinary code — both
examples below do exactly that. A signature or a token you were issued is not a credential of yours, so it may be
returned, stored and logged like any other string.

The rule follows the value through locals, concatenation, interpolation, ternaries and string helpers, so renaming it
on the way out does not evade it. It stops at a call into **your own** functions: `Sign(Secret.K, msg)` hands the key
to code the compiler cannot see, and what that code does with it is yours to get right. So this confines an
*accidental* leak, not a determined one — and it could not be otherwise, since giving the key to an outbound call is
the sanctioned use.

What is persisted is the ciphertext on the secret's own row, and nothing else — a function's expression tree carries
only the NAME.

### Do secrets work under `osy test`?      {#in-a-test}
`osy test` runs against a throwaway branch of your app built from source, and your project's `.secrets` values travel
with the run — so a `[Test]` that exercises a signature check reads the same key the app does, and the credential
path is testable rather than the one part you have to take on faith.

## Examples       {#examples}
Verifying a signed carrier webhook — the key comes from the secret store, and the tag is compared in constant time:

```osy title="verify an inbound signature" test app=function-secret-read
app.Secrets = [ new Secret("CarrierWebhook") ];

bool IsAuthenticScan(string trackingNumber, string location, string signature) {
  var expected = Crypto.HmacSha256Hex(Secret.CarrierWebhook, trackingNumber + "|" + location);
  return Crypto.FixedTimeEquals(expected, signature);
}
```

Sending one outbound — the same read, used as a bearer token rather than a signing key:

```osy title="authenticate an outbound call" test app=function-secret-read-outbound
// `use` is a MANIFEST declaration — it belongs in your app.osy, not in a model file.
app Dispatch {
  model "model/**/*.osy";
  use Osysharp.Http;
}

app.Secrets = [ new Secret("DispatchApi") ];

string FetchManifest(string depot) {
  var headers = new Dictionary<string, string>();
  headers.Add("Authorization", "Bearer " + Secret.DispatchApi);
  var r = Http.Get("https://api.example.com/manifests/" + depot, headers);
  return r.IsSuccess ? r.Body : "";
}
```

## See also       {#see-also}
- [declaring secrets (app.Secrets)](https://osysharp.com/reference/config/secrets/) — `app.Secrets`, where a secret is declared and named
- [Giving a secret its value](https://osysharp.com/reference/local/giving-a-secret-its-value/) — `osy secret set`, and which secrets are still empty
- [Crypto.HmacSha256Hex and Crypto.FixedTimeEquals](https://osysharp.com/reference/function/crypto-hmac/) — verifying a signature with the key you just read, in constant time
- [execution side](https://osysharp.com/reference/function/execution-side/) — why an expression that reads a secret pins its code to the server
