A presigned Amazon S3 URL is a bearer credential: anyone who holds the link can use it until it expires, and the long lived ones end up pasted into code, logs, tickets and chat where nobody is tracking them. Bucket policies and Block Public Access settings say nothing about these links, because a presigned request is signed by a principal that already has access. This guide covers one narrow job: finding presigned URLs that have leaked or that live far longer than they should, working out which signing credential sits behind each one, and shutting the exposure down without breaking legitimate sharing. It is written for cloud security engineers, detection engineers and platform teams who own S3. For bucket hardening and public bucket checks, see the Amazon S3 Security Guide and the Cloud Storage Exposure Playbook.
Source note: AWS behaviour described here summarises the official AWS documentation listed at the end, as of 12 November 2026. Confirm against the official text before relying on it.
Key takeaways
- The URL carries its own identity. The X-Amz-Credential parameter names the access key ID that signed it, so every leaked link points straight at a credential you can investigate.
- Lifetime depends on the signer. A URL signed with IAM user keys can last up to 7 days; one signed with role or STS credentials dies when that session ends, whatever expiry was requested.
- You cannot revoke a single URL. The only kill switches are the signing credential (deactivate the key or revoke the role sessions) or a policy that denies the request.
- AWS logs redact the signature, your tools may not. S3 server access logs and CloudTrail data events strip X-Amz-Signature, but proxies, clients and application logs often record the full working link.
- s3:signatureAge and s3:authType are the guardrails. An SCP, RCP or bucket policy can cap how old a signature may be, or block query string authentication for roles that never need it.
How a presigned URL works and why it leaks
By default S3 objects are private. A principal with access can create a presigned URL that grants time limited permission for one operation, usually GET or PUT on one object. AWS documents that the credentials used by the presigned URL are those of the AWS user who generated it, and that a presigned URL cannot grant access the signing principal does not already have. Generation itself cannot be blocked by policy, because signing is a local Signature Version 4 calculation that never calls AWS. Control therefore sits at request time, when S3 evaluates the signature, the expiry and any policy conditions.
The URL is reusable until it expires, and S3 checks expiry only when a request starts: AWS notes that a download already in progress continues past the expiry time, but a restart fails. That makes these links attractive to share and easy to forget. Typical places they surface:
- Code and fixtures: a generated URL pasted into a test, a README, a seed script or a mobile app config to "just make the download work".
- Logs: reverse proxy, CDN, load balancer and application request logs that record full URIs, including the query string.
- Tickets and chat: support staff sharing a customer export or a crash dump link in Jira, ServiceNow, Slack or Teams.
- Browser captures: HAR files attached to support cases, which hold every request URL. See the HAR Files and Session Token Leakage Guide.
Reading a presigned URL: the parameters that matter
A SigV4 presigned URL carries everything needed to judge its risk in its query string. Parsing these fields lets a scanner score each finding without calling AWS.
| Parameter | What it contains | What it tells the investigator |
|---|---|---|
| X-Amz-Algorithm | AWS4-HMAC-SHA256 for SigV4 (AWS4-ECDSA-P256-SHA256 for SigV4a) | Confirms the string is a signed AWS request, not just an S3 path |
| X-Amz-Credential | Access key ID, then the scope YYYYMMDD/region/service/aws4_request | Which key signed it; the region; that the service is s3 |
| X-Amz-Date | Signing time in ISO 8601 basic format, for example 20261112T093000Z | Start of the validity window |
| X-Amz-Expires | Validity in seconds from X-Amz-Date | Requested lifetime; 604800 means the 7 day maximum |
| X-Amz-Security-Token | Session token, present only when temporary credentials signed the URL | Signed by a role or STS session, so real lifetime is capped by that session |
| X-Amz-SignedHeaders | Lowercase header names covered by the signature, often just host | Whether extra headers are needed to use the link |
| X-Amz-Signature | 64 hex character signature | The secret part: with it the link works, without it the link is useless |
The prefix of the access key ID in X-Amz-Credential is the quickest triage signal. IAM documents AKIA as the prefix for access keys and ASIA as the prefix for temporary (AWS STS) access key IDs. An AKIA key means a long term IAM user key signed the link, so the URL may live for up to 7 days and the key itself is probably stored somewhere in your estate. An ASIA key means a role or federated session, whose lifetime bounds the URL.
How long a leaked URL stays live
The effective expiry is the earlier of the requested expiry and the expiry of the signing credential. AWS also states the URL stops working if the credential is revoked, deleted or deactivated.
| Signer | Maximum effective life (AWS documentation) | Exposure rating if leaked |
|---|---|---|
| S3 console share | 1 minute to 12 hours | Low to medium |
| IAM user access key (AKIA), CLI or SDK | Up to 7 days | High, and the key itself needs finding |
| IAM role session via AssumeRole (ASIA) | Session duration; default 1 hour | Medium, depends on session length |
| EC2 instance role | Duration of role credentials, typically 6 hours | Medium |
| Amazon ECS task role | Credentials typically rotate every 1 to 6 hours | Medium |
| Other STS temporary credentials | Duration of the temporary credentials | Depends on issued duration |
A useful scanner rule: compute X-Amz-Date plus X-Amz-Expires, flag anything still valid now, and raise severity when the key prefix is AKIA or the requested lifetime exceeds your policy (for example more than one hour for customer data).
Finding presigned URLs in code, logs and tickets
The same pattern works across every store: look for an S3 hostname or path together with X-Amz-Signature, then parse the other parameters. AWS publishes the regular expression X-Amz-Signature=[a-f0-9]{64} for matching the signature as it appears in a URI.
Code repositories
- Custom secret scanning pattern: GitHub secret scanning supports custom patterns at repository, organisation and enterprise level. Define the signature pattern, use "Save and dry run" (it returns a sample of up to 1,000 matches) to tune false positives, then publish it so the full Git history on all branches is scanned. Push protection can be turned on for the pattern afterwards. Other scanners accept equivalent custom rules. See the Git Secret Scanning Guide.
- Signing keys alongside URLs: GitHub's built in patterns cover AWS access key IDs with secret keys and AWS session tokens, with push protection and validity checks. A repository that contains presigned URLs often contains the key that made them.
- Front end and mobile bundles: scan built JavaScript and app packages, not only source, for both AKIA strings and signed URLs. Signing in the browser means the key is shipped to every visitor.
Logs
- AWS native logs are already redacted: AWS states that S3 server access logs and CloudTrail data events record the request URI with X-Amz-Signature redacted, so they are safe to search for usage but will not contain working links.
- Intermediaries are the risk: clients can log any part of a request, and proxies commonly log URIs including X-Amz-Signature. Search proxy, CDN, WAF, API gateway and application logs for the signature pattern.
- Mask at ingestion: redact the X-Amz-Signature value or the whole query string before storage. CloudWatch Logs data protection can mask matches using a custom data identifier built on the AWS regular expression; SIEM pipelines can apply the same pattern.
Tickets, chat and documents
- Export and scan: use the ticketing or chat platform's export or eDiscovery API to search for the S3 hostname plus X-Amz-Signature. Prioritise support queues and customer facing channels, where data exports are shared most.
When the signing key is a long term secret found during this work, handle it as a leaked credential. See the Developer Secret Scanning Triage guide.
Tracing the credential and checking for use
- Extract the access key ID from X-Amz-Credential and note the bucket, object key, region, signing date and requested expiry.
- Map the key to a principal. For AKIA keys, find the IAM user that owns the key; for ASIA keys, look up the session in CloudTrail to find the assumed role and the workload that called AssumeRole.
- Confirm data event logging. CloudTrail does not log S3 object level data events by default and they carry additional charges. If they are on for the bucket (resource type AWS::S3::Object through advanced event selectors), you can see each GetObject or PutObject made with the link.
- Find presigned use. In CloudTrail, additionalEventData.AuthenticationMethod shows QueryString for query string authentication and AuthHeader for header authentication, with userIdentity.accessKeyId naming the key. In S3 server access logs, the Authentication Type field shows QueryString for a presigned URL, and the Requester field shows the IAM user or the assumed role ARN.
- Look for unexpected callers. Compare source IP addresses and user agents against where the link was meant to go. AWS notes that conditions such as aws:SourceIp are evaluated against whoever sends the request, not the system that signed it.
Containing a leaked or over long presigned URL
| Situation | Containment action | Side effects to plan for |
|---|---|---|
| Signed by an IAM user key (AKIA) | Deactivate the access key, then rotate and delete it once the owner moves to a new one | Every URL that key signed stops working, plus any other use of the key |
| Signed by a role session (ASIA) | Use Revoke active sessions on the role, which adds the inline policy AWSRevokeOlderSessions, or attach a deny on aws:TokenIssueTime | All sessions of that role issued before the cut off lose access; cannot be used on service linked roles; Identity Center roles are revoked in Identity Center |
| Single sensitive object, key cannot be disabled quickly | Add a bucket policy deny for that object key with s3:signatureAge or s3:authType, or move or re-encrypt the object | Legitimate presigned access to that object also stops |
| Link still valid in a third party system | Contain the credential first, then ask for the record to be removed | Deleting the message alone leaves the link usable |
The aws:TokenIssueTime condition is present only in requests signed with temporary credentials, so it does not affect IAM user keys. Changes are eventually consistent, so verify by testing the leaked link after a few minutes. For the wider key response, see the Cloud Access Key Leak Playbook.
Preventing long lived presigned URLs
Guardrails from AWS prescriptive guidance
- Cap signature age: s3:signatureAge, in milliseconds, limits the time between the signed date and use. AWS shows an SCP denying s3:* when the value exceeds 900000 (15 minutes) and advises never going below 60 seconds because clock skew can reject valid requests. It only shortens validity and applies only to presigned requests.
- Block query string authentication where not needed: s3:authType is REST-QUERY-STRING for presigned URLs, POST for presigned POST and REST-HEADER for normal signed calls. An SCP that denies anything other than REST-HEADER removes presigned access for workload roles, but also blocks S3 console uploads and downloads, so grant a tag based exception to the roles that genuinely sign links.
- Retire SigV2: the same guidance denies s3:signatureversion equal to AWS, which blocks Signature Version 2.
- Protect the exception tag: exceptions use aws:PrincipalTag (for example long-presigned-allowed). Control who can set the tag through an SCP or your identity provider, or the exception becomes a bypass.
- Pick the right policy layer: an SCP covers principals in member accounts; an RCP covers buckets in the organisation (including requests from outside principals); a bucket policy targets chosen buckets and can add an aws:PrincipalOrgID deny for presigned requests from principals outside your organisation. A custom AWS Config rule can check that each bucket policy carries the guardrail.
- Restrict the network path: aws:SourceIp, aws:SourceVpc and aws:SourceVpce in a bucket or identity policy limit where a presigned link can be used from.
Engineering patterns
- Sign server side with role credentials: issue links from a back end that uses a short session, never from a browser or app holding IAM user keys. AWS notes permanent credentials are not limited by signatureAge, while temporary credentials can be as short as 15 minutes.
- Use a dedicated signing role per use case with access only to the prefix it serves, so one leak exposes one dataset.
- Issue on demand: generate the link when the user clicks, with expiry measured in minutes, rather than embedding a link in an email that must last days.
Decision guidance
- Small organisation, few buckets: start with a bucket policy signatureAge deny on buckets holding customer or regulated data and a repository custom pattern. That covers most exposure with little operational risk.
- Large organisation with AWS Organizations: roll out an RCP for signature age across all buckets and an SCP restricting s3:authType for workload roles, with tagged exceptions owned by the platform team.
- Partners need multi day access: prefer a cross account role or a dedicated access point over a 7 day link; if a long link is unavoidable, sign it with a dedicated key or role scoped to one prefix and log every use.
- Header only versus age cap: blocking query string authentication is stronger but breaks console and presigned flows; capping age keeps flows working and limits the damage window. Many teams run both, with the auth type rule on workload roles only.
Rolling out the controls safely
- Inventory existing use first. AWS recommends identifying current presigned usage before enabling guardrails. Turn on CloudTrail data events or server access logs for target buckets and count QueryString requests by principal and typical signature age for two to four weeks.
- Run the scanners in report mode across repositories, proxy logs and ticket exports; triage findings by key prefix and remaining life.
- Pilot the guardrail as a bucket policy on one non production bucket, then one production bucket with known consumers, with the exception tag ready.
- Promote to RCP or SCP at a test organisational unit, then wider, communicating the maximum allowed link age to developers.
- Roll back by detaching the policy or applying the exception tag to the affected role; keep the previous policy version recorded so restoration is one change.
- Turn on push protection for the custom pattern once false positives are under control.
Detection and measurement
- Alert on: QueryString requests from AKIA keys; presigned requests from IP ranges outside expected consumers; new principals starting to use query string authentication; requests denied by the signatureAge guardrail (they show where long links are still being made).
- Metrics that show it is working:
- Share of presigned requests signed by temporary credentials (target: close to 100 percent).
- 95th percentile requested lifetime of presigned URLs seen in code and logs.
- Number of still valid presigned URLs found per month in repositories, logs and tickets, and median time to contain.
- Percentage of buckets with sensitive data covered by a signatureAge guardrail.
Framework mapping
| Framework | Reference | How it applies |
|---|---|---|
| MITRE ATT&CK | T1530 Data from Cloud Storage | Using a leaked link to read objects |
| MITRE ATT&CK | T1552.001 Credentials In Files; T1552.008 Chat Messages | Links and signing keys left in files and chat |
| MITRE ATT&CK | T1078.004 Cloud Accounts | Abuse of the IAM user or role behind the signature |
| NIST SP 800-53 Rev 5 | AC-3, AC-21, IA-5, AU-2, AU-12, SI-4 | Access enforcement, information sharing, authenticator lifetime, logging and monitoring |
| NIST CSF 2.0 | PR.AA-01, PR.AA-05, PR.DS-01, DE.CM-09, RS.MI-01 | Credential management, least privilege, data at rest, monitoring, containment |
| ISO/IEC 27001:2022 Annex A | 5.15 Access control, 5.17 Authentication information, 8.12 Data leakage prevention, 8.15 Logging | Policy for sharing links, protection of signing secrets, leak detection, log handling |
Common failure patterns
- Signing in the browser with IAM user keys. The key ships to every visitor, and every URL it makes can last 7 days. The Football Australia AWS keys exposure shows how keys in public page source were reported to open 127 buckets.
- Deleting the chat message and calling it fixed. The link keeps working until the credential is contained or the URL expires.
- Trusting log redaction everywhere. AWS logs redact the signature, but proxy and application logs that feed a widely accessible SIEM may hold working links.
- Setting a short ExpiresIn but signing with a long lived key. The expiry in code is easy to change later; a signatureAge policy enforces the limit regardless of the code.
- Rolling out s3:authType without an inventory. Console uploads and downloads and legitimate presigned flows break, and the guardrail gets removed under pressure.
- Exception tags anyone can set. If developers can tag their own roles with the exception, the guardrail no longer applies.
Practitioner checklist
- Add a custom secret scanning pattern for X-Amz-Signature and dry run it across repositories.
- Search proxy, CDN, gateway and application logs for working presigned URLs and mask the signature at ingestion.
- Scan ticket, chat and HAR file exports for S3 links carrying a signature.
- For each finding, extract the access key ID, classify AKIA or ASIA and compute remaining life.
- Contain by deactivating the key or revoking the role sessions, then verify the link fails.
- Enable CloudTrail S3 data events or server access logs on sensitive buckets and review QueryString use.
- Move signing to server side code using short role sessions scoped to one prefix.
- Deploy an s3:signatureAge guardrail (bucket policy, then RCP or SCP) with a protected exception tag.
- Restrict s3:authType for workload roles that never need presigned access.
- Track the metrics above monthly and report still valid URLs found.
Standards and references
- AWS: Download and upload objects with presigned URLs
- AWS: Sharing objects with presigned URLs
- AWS Prescriptive Guidance: Presigned URL additional guardrails
- AWS Prescriptive Guidance: Logging interactions with presigned URLs
- AWS: Amazon S3 server access log format
- AWS: Revoke IAM role temporary security credentials
- AWS: Logging data events with CloudTrail
- GitHub Docs: Defining custom patterns for secret scanning
Related NHI Mgmt Group resources: Amazon S3 Security Guide · Cloud Storage Exposure Playbook · Cloud Access Key Leak Playbook · Git Secret Scanning Guide