Description
The assertions: block renders engine-derived state onto the upstream request as headers, and SourcePath (crates/ppe-core/src/assertions/source.rs:122) enumerates the slots an entry may read. None of them is a secret, so a target sitting behind a static API key has no path today short of a token delegator, which is per-request exchange machinery that a shared static credential does not need.
Add a secret.<name> source resolving to a value declared in the secrets.values block. The engine renders the header; no plugin and no PDP ever holds the bytes.
assertions:
request:
- header: X-API-Key
from: secret.legacy_api_key
The path names a declared secret, not a provider and a reference. That is what keeps the module's existing property intact: the engine originates every value a request entry asserts, so the legitimate set is finite and anything unnamed can be withheld. A raw reference in the path would make the addressable set whatever the provider's credentials can reach, with no list an operator could audit.
Acceptance criteria
SourcePath gains a secret variant, authored as secret.<name>. Provider names and references never appear in an assertion path.
- The name must resolve to a declared
secrets.values entry at config load. An unknown name is a config error, not a runtime miss.
- The read is sync, from the value resolved at startup. No fetch on the request path.
- Valid in the request direction only. A response entry naming a secret is a config error, since a secret is not something an upstream told us.
on_missing applies as it does for any other source. The response floor is unaffected.
- The effective-policy artifact prints the secret name and never the value.
- The value never reaches a PDP, a plugin payload, a log, a deny message, or the audit record.
SourcePath::capability() returns the capability that would gate a plugin's read of this slot, consistent with the existing table, even though nothing reads it on this path.
- Tests: injection onto the upstream request, an unknown name rejected at load, a response-direction entry rejected, a rotated secret picked up after refresh, and redaction in the artifact.
Notes
This is the outbound half of what #65 describes. Whether PPE also wants an inbound API key verifier, where a caller presents a key and PPE maps it to an identity, is a separate call; it is a different feature that happens to share the words "API key", and it is what #64 is a dependency of.
Depends on
#92
Description
The
assertions:block renders engine-derived state onto the upstream request as headers, andSourcePath(crates/ppe-core/src/assertions/source.rs:122) enumerates the slots an entry may read. None of them is a secret, so a target sitting behind a static API key has no path today short of a token delegator, which is per-request exchange machinery that a shared static credential does not need.Add a
secret.<name>source resolving to a value declared in thesecrets.valuesblock. The engine renders the header; no plugin and no PDP ever holds the bytes.The path names a declared secret, not a provider and a reference. That is what keeps the module's existing property intact: the engine originates every value a request entry asserts, so the legitimate set is finite and anything unnamed can be withheld. A raw reference in the path would make the addressable set whatever the provider's credentials can reach, with no list an operator could audit.
Acceptance criteria
SourcePathgains a secret variant, authored assecret.<name>. Provider names and references never appear in an assertion path.secrets.valuesentry at config load. An unknown name is a config error, not a runtime miss.on_missingapplies as it does for any other source. The response floor is unaffected.SourcePath::capability()returns the capability that would gate a plugin's read of this slot, consistent with the existing table, even though nothing reads it on this path.Notes
This is the outbound half of what #65 describes. Whether PPE also wants an inbound API key verifier, where a caller presents a key and PPE maps it to an identity, is a separate call; it is a different feature that happens to share the words "API key", and it is what #64 is a dependency of.
Depends on
#92