Source Provenance
SourceProvenance is AppTheory’s portable, structured source-IP contract for HTTP requests. It is available at every tier including P0, and it is derived exclusively from AWS provider request-context fields. AppTheory never trusts Forwarded or X-Forwarded-For for this contract.
Why this exists
X-Forwarded-For and Forwarded are viewer-controlled headers unless your product has its own separate trusted-proxy model. A handler that reads them for security decisions — rate limiting by IP, geo gating, fraud scoring — is reading attacker-controlled input. AppTheory closes that door by surfacing only the provider-observed source IP, in canonical form, through a single accessor.
The accessor surface
| Concern | Go | TypeScript | Python |
|---|---|---|---|
| Structured type | SourceProvenance |
SourceProvenance |
SourceProvenance |
| Request field | Request.SourceProvenance |
Request.sourceProvenance |
Request.source_provenance |
| Context accessor | ctx.SourceProvenance() |
ctx.sourceProvenance() |
ctx.source_provenance() |
| Convenience IP | ctx.SourceIP() |
ctx.sourceIP() |
ctx.source_ip() |
| APIGW v2 test option | HTTPEventOptions.SourceIP |
sourceIp |
source_ip |
| Lambda URL test option | HTTPEventOptions.SourceIP |
sourceIp |
source_ip |
The shape
The structured value has four fields:
| Field | Meaning |
|---|---|
source_ip |
Canonical parsed source IP string, or "" when invalid/unknown. |
provider |
apigw-v2, lambda-url, apigw-v1, or unknown. |
source |
provider_request_context or unknown. |
valid |
true only when the provider supplied a parseable source IP. |
Provider mapping
| AWS event source | Field read | provider value |
|---|---|---|
| API Gateway v2 HTTP API | requestContext.http.sourceIp |
apigw-v2 |
| Lambda Function URL | requestContext.http.sourceIp |
lambda-url |
| API Gateway v1 REST proxy | requestContext.identity.sourceIp |
apigw-v1 |
| ALB target group | (none) | unknown |
| Anything else | (none) | unknown |
ALB source values are intentionally not supported in this contract because ALB does not surface a reliable, untrusted-header-free source IP in the same way API Gateway and Lambda URLs do.
Examples
Go
app.Get("/source", func(ctx *apptheory.Context) (*apptheory.Response, error) {
return apptheory.JSON(200, map[string]string{"source_ip": ctx.SourceIP()})
})
event := testkit.APIGatewayV2Request("GET", "/source", testkit.HTTPEventOptions{
SourceIP: "2001:DB8::1",
})
TypeScript
app.get("/source", (ctx) => json(200, { source_ip: ctx.sourceIP() }));
const event = buildAPIGatewayV2Request("GET", "/source", {
sourceIp: "2001:DB8::1",
});
Python
app.get("/source", lambda ctx: json(200, {"source_ip": ctx.source_ip()}))
event = build_apigw_v2_request("GET", "/source", source_ip="2001:DB8::1")
Canonical IP form
Valid IPs are parsed and re-emitted in canonical form before they become public response or handler strings. The fixture pins this explicitly:
input: "2001:DB8::1"
emitted: "2001:db8::1"
All three runtimes canonicalize identically. If you see Go and TypeScript disagreeing on source_ip, it is a runtime bug, not a fixture interpretation issue — the canonical form is the contract.
Fail-closed behavior
When the provider does not supply a source IP, or supplies one that does not parse:
provider: "unknown"
source: "unknown"
source_ip: ""
valid: false
The runtime does not fall back to X-Forwarded-For. There is no flag to enable fallback. If you have a trusted-proxy model in front of AppTheory, derive the trusted source in your own middleware and stash it on the context — but do not extend SourceProvenance to read forwarding headers.
What’s not in scope
- Trust models for
Forwarded/X-Forwarded-For— not handled. Build your own middleware if needed. - GeoIP — not handled. The contract surface is the IP only.
- Rate-limiting by IP — handled separately from
SourceProvenance. In Go,RateLimitMiddlewarecan useSourceIPas the bucket key; TypeScript and Python callers should apply the same policy through their P2 policy hooks or limiter primitives.
Related
- HTTP Runtime tiers —
SourceProvenanceis available at P0 - Sanitization — safe logging of source IPs
- v1 Security Migration — older code paths and how to update them