Redact sensitive URLs before sharing a description
Before sharing a reset, invitation or signed link, remove its credentials, path identifiers, query values and fragment tokens. Keep a description of the structure and a small set of known parameter names so another person can understand the problem without receiving the original access link. The function below makes that working copy locally; a person must still check the hostname, parameter names and surrounding context before sharing it.
Make a description, not a working access link
A URL can carry permission as well as an address. A password-reset token, an invitation identifier and an object-storage signature may be useful to anyone who obtains them. Amazon describes presigned object URLs as bearer tokens; their exact lifetime also depends on the credentials used to issue them1. Treat the complete original as sensitive before deciding which service or person should see it.
Do not paste the original into a public issue, chat preview, search engine or analysis service merely to ask whether it is sensitive. Open your own trusted editor, make a separate copy and work on that copy. If you need to compare a hostname, the passive URL inspector explains what the address contains without looking up the destination. A structural inspection does not test the link’s permissions.
The goal here is a useful description, not a shortened functioning link. The usable_link: false field is descriptive metadata: it does not disable navigation. Keep the result in a code span or plain-text attachment rather than turning it into a clickable link. Replacing the signature while keeping a document’s private name may still disclose something. Removing the query while leaving an invitation token in the path can leave the credential intact. Start by removing values broadly, then explain any necessary non-sensitive detail in separate prose.
A complete local redaction function
Save this original MIT example as redact.mjs. It uses the built-in URL parser and never makes a request. The example files and commands include the same function, the synthetic cases and the assertions used for this page.
// SPDX-License-Identifier: MIT
export function redactAddress(input) {
if (typeof input !== 'string' || input.length > 8192 ||
/[\u0000-\u0020\u007f]/u.test(input) || !/^https?:\/\//iu.test(input)) {
throw new Error('Expected one absolute HTTP(S) address without spaces');
}
let url;
try {
url = new URL(input);
} catch {
throw new Error('Cannot parse this address; do not share the original');
}
const removed = [];
if (url.username || url.password) {
removed.push('credentials');
}
const path = url.pathname.split('/').map((segment, index) => {
if (!segment) return '';
removed.push(`path[${index}]`);
return '[PATH]';
}).join('/');
const allowedNames = new Set([
'token', 'code', 'state', 'access_token', 'refresh_token', 'id_token',
'x-amz-credential', 'x-amz-signature', 'x-amz-expires',
'sp', 'se', 'sig', 'sv', 'st', 'lang', 'page'
]);
function redactParameters(text, area, strict = false) {
if (!text) return '';
const items = text.split('&');
if (strict && !items.every(item => {
const equal = item.indexOf('=');
const name = item.slice(0, equal);
return equal > 0 && /^[A-Za-z][A-Za-z0-9_-]{0,31}$/u.test(name) &&
allowedNames.has(name.toLowerCase());
})) {
removed.push(area);
return '[FRAGMENT]';
}
const result = new URLSearchParams();
for (const item of items) {
const equal = item.indexOf('=');
const [name = ''] = new URLSearchParams(item).keys();
const key = equal <= 0 ? '[OPAQUE]' :
(allowedNames.has(name.toLowerCase()) ? name : '[NAME]');
result.append(key, '[REDACTED]');
removed.push(`${area}.${key}`);
}
return result.toString();
}
const query = redactParameters(url.search.slice(1), 'query');
const safeFragment = redactParameters(url.hash.slice(1), 'fragment', true);
return {
redacted_url: `${url.protocol}//${url.host}${path}` +
(query ? `?${query}` : '') +
(safeFragment ? `#${safeFragment}` : ''),
removed,
review_required: ['hostname', 'parameter names', 'context and attachments'],
usable_link: false
};
}
The input guard accepts one absolute HTTP or HTTPS address, without spaces or control characters. A parse failure produces a fixed error message instead of echoing the input. That matters when a caller puts errors into an application log: the error should not become another copy of the secret. The function also caps input length; it is not a parser for arbitrary pasted messages.
The parser produces a normalized address. The function then keeps its scheme and host, omits username and password information, and replaces every non-empty path segment. The resulting path preserves a rough segment layout rather than its original spelling. Normalization may already have resolved dot segments or encoded characters, so the output is unsuitable for reconstructing an exact original request.
Every query value becomes the same marker, including empty values and apparently harmless preferences. The function examines each original item for an equals sign before using URLSearchParams: a value-less item becomes an opaque marker, so a reset token cannot survive merely by becoming a parameter name. Only sixteen approved names are retained; other names become [NAME]. URLSearchParams preserves ordered entries and permits repeated names, so approved duplicates remain separate2. Markers may appear percent-encoded when serialized; that representation does not restore a removed value.
Walk through the common cases
For a reset address such as https://account.example.test/reset?token=RESET_SYNTHETIC, both the path text and token value disappear. The output still explains that an HTTP query parameter named token was present. For an invitation such as https://invite.example.test/join/INVITE_SYNTHETIC, replacing each path segment matters because there is no query string to remove.
A cloud-storage example keeps names such as X-Amz-Credential, X-Amz-Signature and X-Amz-Expires, while replacing all their values and the object path. The reader can recognize a signed-link shape without seeing a credential, signature, private object name or expiry. This is a description of the synthetic input, not a check of Amazon’s signature format or validation rules.
A SAS-style example treats sp, se and sig the same way. Microsoft’s documentation describes the signature and related parameters as part of the authorization information appended to a resource URI3. Removing only sig is therefore an unnecessarily narrow sharing policy: other values may reveal permissions, timing or resource context even after the signature no longer works.
OAuth examples need attention to both query and fragment. The function removes a query code, a state value and a fragment access_token. A fragment is retained as redacted pairs only when every item has an equals sign, a short ASCII name and a name on the same allowlist. Otherwise the entire fragment becomes [FRAGMENT], including routes, value-less items and base64-like strings. That strict rule avoids mistaking a token ending in equals signs for a useful parameter name. RFC 9700 discusses authorization-code and token leakage through browser history and other channels; a fragment should not be treated as harmless merely because it is handled differently from a query4.
| Synthetic case | Structure retained | Values removed |
|---|---|---|
| Reset | Host and token name | Reset token and path text |
| Invitation | Host and segment count | Invitation and route segments |
| Presigned object | Signing parameter names | Object name and every parameter value |
| SAS-style link | sp, se, sig names | Permissions, expiry and signature values |
| OAuth callback | code and state names | Code and state |
| Fragment token | Fragment parameter names | Token and state |
| Fragment route | Opaque fragment marker | Entire route and its values |
| Repeated parameters | Duplicate names and order | Every repeated value |
The twenty-one accepted cases include credentials, encoded content, ordinary preferences and a minimal address. Added regressions cover a bare reset-token query, a base64-like fragment, a mixed value-less fragment, unknown names, an encoded name, a trailing fragment item and a value-less approved name. Each synthetic secret is absent from the complete returned object. Six refusals cover relative input, a non-web scheme, whitespace, a control character, malformed input and excessive length. The run record preserves synthetic observations and exact runtime information; it contains no real reset links or customer records.
Review the copy before sharing
Download the redaction worksheet and use it on the copy. First confirm that no original value survives in an attachment, screenshot, quoted message or filename. Then inspect the hostname: an account identifier can be placed in a subdomain, and this function intentionally leaves hostnames readable. Replace identifying labels manually when the recipient only needs the URL’s shape.
Next review the remaining labels and context. The allowlist keeps conventional names such as token, code, sig and X-Amz-Signature; it replaces arbitrary names instead of assuming that every key is harmless. Extending that allowlist would change the sharing boundary, so review additions deliberately. Even an approved label can reveal the kind of workflow involved. Do not change private original evidence silently when preparing a separate public description.
Keep the private original only where the authorized investigation requires it. Use a separate description for the action, expected result and time of the problem. The submission-privacy guide and lookup-versus-submission comparison help distinguish local preparation from sharing with a provider. Redaction does not revoke a credential that has already escaped; use the issuing service’s documented owner controls if revocation is necessary.
Not covered on this page
- Verifying signatures, permissions or whether a link still works.
- Automatically identifying secrets in hostnames or judging the privacy of the remaining context.
- Revocation, account recovery or the security of a particular service.
- Recovering an exact original request from the normalized redacted copy.
Sources
-
Amazon S3, Download and upload objects with presigned URLs, documentation read 25 October 2026. ↩
-
MDN, URLSearchParams, documentation read 25 October 2026. ↩
-
Microsoft Learn, Grant limited access using shared access signatures, documentation read 25 October 2026. ↩
-
IETF, RFC 9700: Best Current Practice for OAuth 2.0 Security, January 2025, read 25 October 2026. ↩
Code on this page is original and MIT licensed.
How we test: methodology. Found a mistake? Corrections.