<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tanya</title>
    <description>The latest articles on DEV Community by Tanya (@tanya_monoware).</description>
    <link>https://dev.to/tanya_monoware</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4048666%2Fd478dfa8-9d4d-40ac-8159-bbba780e392d.jpg</url>
      <title>DEV Community: Tanya</title>
      <link>https://dev.to/tanya_monoware</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tanya_monoware"/>
    <language>en</language>
    <item>
      <title>How do you regression-test a ReDoS fix without hanging CI?</title>
      <dc:creator>Tanya</dc:creator>
      <pubDate>Sat, 15 Aug 2026 06:22:21 +0000</pubDate>
      <link>https://dev.to/tanya_monoware/how-do-you-regression-test-a-redos-fix-without-hanging-ci-1al3</link>
      <guid>https://dev.to/tanya_monoware/how-do-you-regression-test-a-redos-fix-without-hanging-ci-1al3</guid>
      <description>&lt;p&gt;A known-bad regex is useful evidence, but putting it directly in the test process can hang the runner before the timeout assertion fires.&lt;/p&gt;

&lt;p&gt;The boundary I am using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;run each adversarial case in a fresh worker thread or child process&lt;/li&gt;
&lt;li&gt;let the parent own a hard timeout and terminate the child&lt;/li&gt;
&lt;li&gt;keep semantic-parity fixtures separate from timing guards&lt;/li&gt;
&lt;li&gt;require the safer replacement to pass both suites&lt;/li&gt;
&lt;li&gt;record the timeout class and bounded elapsed time as evidence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Browser workers have the same trap: startup time should not consume the execution budget, and output limits matter alongside time limits.&lt;/p&gt;

&lt;p&gt;Disclosure: I maintain MonoTools. I recently tightened its browser-local Regex Tester around a 300 ms post-startup Worker budget, named groups, replacement previews, and regression cases:&lt;br&gt;
&lt;a href="https://monoware.app/tools/regex-tester?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=regex_redos_ci&amp;amp;utm_content=failure_receipt" rel="noopener noreferrer"&gt;try the bounded tester&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What does your team treat as a deterministic CI failure receipt for ReDoS: an exit code, a timeout class, an elapsed-time range, or something else?&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>javascript</category>
      <category>security</category>
      <category>testing</category>
    </item>
    <item>
      <title>CHIPS Won't Restore a Third-Party Chat Session by Itself</title>
      <dc:creator>Tanya</dc:creator>
      <pubDate>Sun, 09 Aug 2026 16:42:01 +0000</pubDate>
      <link>https://dev.to/tanya_monoware/chips-wont-restore-a-third-party-chat-session-by-itself-31mj</link>
      <guid>https://dev.to/tanya_monoware/chips-wont-restore-a-third-party-chat-session-by-itself-31mj</guid>
      <description>&lt;p&gt;A partitioned cookie solves one narrow problem: it lets an embedded origin keep a separate cookie jar for each top-level site. That can help an iframe chat widget remember a handle, but it does not reconstruct a conversation by itself.&lt;/p&gt;

&lt;p&gt;Imagine &lt;code&gt;chat.example&lt;/code&gt; embedded on &lt;code&gt;shop-a.com&lt;/code&gt; and &lt;code&gt;shop-b.com&lt;/code&gt;. With CHIPS, the browser keys a Partitioned cookie by both the embedded site and the top-level site. The cookie available on shop-a is not the cookie available on shop-b. That reduces cross-site tracking, but it also means CHIPS is not a shared identity system. The distinction matters when debugging a session that seems to disappear.&lt;/p&gt;

&lt;h2&gt;
  
  
  What still has to exist
&lt;/h2&gt;

&lt;p&gt;A reliable restore flow needs three pieces outside the browser policy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An opaque conversation handle. Do not put the transcript, email address, or customer record in the cookie.&lt;/li&gt;
&lt;li&gt;A server-side mapping from that handle to the conversation the user is allowed to resume.&lt;/li&gt;
&lt;li&gt;A fallback when storage is unavailable, such as an explicit sign-in or a first-party continuation flow.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A typical candidate looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Set-Cookie: __Host-chat=opaque-id; Path=/; Secure; HttpOnly; SameSite=None; Partitioned
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Partitioned&lt;/code&gt; requires &lt;code&gt;Secure&lt;/code&gt;. A cross-site iframe normally also needs &lt;code&gt;SameSite=None&lt;/code&gt;. The &lt;code&gt;__Host-&lt;/code&gt; prefix keeps the cookie host-bound by forbidding &lt;code&gt;Domain&lt;/code&gt; and requiring &lt;code&gt;Path=/&lt;/code&gt;. &lt;code&gt;HttpOnly&lt;/code&gt; is appropriate when only the server needs the handle.&lt;/p&gt;

&lt;p&gt;None of those attributes prove that the browser accepted the cookie, retained it, or will send it in the context you care about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test behavior, not API presence
&lt;/h2&gt;

&lt;p&gt;Checking that &lt;code&gt;localStorage&lt;/code&gt;, IndexedDB, or &lt;code&gt;document.cookie&lt;/code&gt; exists is not enough. Privacy modes, partitioning, permissions, sandbox flags, and browser policy can still make reads or writes fail.&lt;/p&gt;

&lt;p&gt;Run a short-lived write/read/delete probe inside the real iframe. Record which operation failed and clean up every temporary value. For cookies, test a disposable candidate and treat the result as evidence for that browser and embedding context, not a universal guarantee.&lt;/p&gt;

&lt;p&gt;I maintain MonoTools. Its &lt;a href="https://monoware.app/tools/storage-probe?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=chips_session_boundary" rel="noopener noreferrer"&gt;Storage Probe&lt;/a&gt; checks localStorage, sessionStorage, IndexedDB, and a short-lived Partitioned cookie candidate locally. It does not request Storage Access permission or claim that a passing probe guarantees durable identity.&lt;/p&gt;

&lt;p&gt;CHIPS is a browser storage boundary. Session restoration is still an application protocol.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>What to Remove from Logs Before Sharing Them</title>
      <dc:creator>Tanya</dc:creator>
      <pubDate>Fri, 07 Aug 2026 12:50:15 +0000</pubDate>
      <link>https://dev.to/tanya_monoware/what-to-remove-from-logs-before-sharing-them-3n6b</link>
      <guid>https://dev.to/tanya_monoware/what-to-remove-from-logs-before-sharing-them-3n6b</guid>
      <description>&lt;p&gt;A useful debug log can also be a credential bundle.&lt;/p&gt;

&lt;p&gt;Authorization headers, session cookies, database URLs, webhook endpoints, API keys, and complete environment-variable dumps often appear beside the error you actually need help with. Removing lines named &lt;code&gt;password&lt;/code&gt; is not enough: many tokens have provider-specific prefixes, while others are just high-entropy strings near an innocent-looking field name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the original private
&lt;/h2&gt;

&lt;p&gt;Do not redact the only copy. Preserve the original inside the system where it was collected, then make a separate sharing copy.&lt;/p&gt;

&lt;p&gt;Before posting that copy to an issue tracker, chat, documentation page, or AI assistant, check at least:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Authorization&lt;/code&gt;, &lt;code&gt;Cookie&lt;/code&gt;, and &lt;code&gt;Set-Cookie&lt;/code&gt; headers.&lt;/li&gt;
&lt;li&gt;Database and cache URLs containing usernames or passwords.&lt;/li&gt;
&lt;li&gt;AWS, GitHub, Google, Stripe, Slack, and similar provider credentials.&lt;/li&gt;
&lt;li&gt;Private-key blocks and webhook URLs.&lt;/li&gt;
&lt;li&gt;Environment assignments containing &lt;code&gt;SECRET&lt;/code&gt;, &lt;code&gt;TOKEN&lt;/code&gt;, &lt;code&gt;PASSWORD&lt;/code&gt;, or &lt;code&gt;API_KEY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Long random-looking values that may be session IDs or custom tokens.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exact provider patterns are high-signal. Generic assignments and entropy checks need more judgement because hashes, fixtures, and public identifiers can look similar to secrets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve the debugging structure
&lt;/h2&gt;

&lt;p&gt;Replace the value, not the surrounding evidence. Keep timestamps, severity, request paths, field names, line numbers, and stack frames when they are safe. Use a consistent marker such as &lt;code&gt;[REDACTED]&lt;/code&gt; so repeated values remain easy to trace without exposing them.&lt;/p&gt;

&lt;p&gt;Then scan the redacted copy again. The second pass catches credentials repeated in a later request, serialized object, exception message, or copied shell command.&lt;/p&gt;

&lt;p&gt;Read the result before sharing it. A scanner can miss an internal token format, and it can flag a harmless random value. Heuristics reduce risk; they do not replace repository scanning, DLP, or a handling policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rotate what already crossed the boundary
&lt;/h2&gt;

&lt;p&gt;Redaction only protects the next copy. If a real credential was already pasted into a ticket, chat, public repository, or external service, assume it may have been read.&lt;/p&gt;

&lt;p&gt;Revoke or rotate it, review the relevant provider logs, remove it from public history where possible, and replace the literal with a secret-manager reference. Deleting the message is cleanup, not proof that the credential remained private.&lt;/p&gt;

&lt;p&gt;I maintain MonoTools. Its Secret Scanner runs this pre-flight in the current browser tab and creates a redacted copy plus a review report:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://monoware.app/guides/remove-secrets-from-logs?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=secret_redaction_guide" rel="noopener noreferrer"&gt;https://monoware.app/guides/remove-secrets-from-logs?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=secret_redaction_guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The scanner is deliberately heuristic. A clean result means “no obvious match was found,” not “this log is safe to publish.”&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>webdev</category>
      <category>debugging</category>
    </item>
    <item>
      <title>How to Repair Almost-JSON Without Guessing</title>
      <dc:creator>Tanya</dc:creator>
      <pubDate>Sun, 02 Aug 2026 11:51:02 +0000</pubDate>
      <link>https://dev.to/tanya_monoware/how-to-repair-almost-json-without-guessing-141g</link>
      <guid>https://dev.to/tanya_monoware/how-to-repair-almost-json-without-guessing-141g</guid>
      <description>&lt;p&gt;AI responses, logs, and configuration snippets often look like JSON without&lt;br&gt;
being valid JSON.&lt;/p&gt;

&lt;p&gt;Typical examples contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Markdown fences around the payload.&lt;/li&gt;
&lt;li&gt;Comments.&lt;/li&gt;
&lt;li&gt;Single-quoted strings.&lt;/li&gt;
&lt;li&gt;Trailing commas.&lt;/li&gt;
&lt;li&gt;Bare object keys.&lt;/li&gt;
&lt;li&gt;Python literals such as &lt;code&gt;True&lt;/code&gt;, &lt;code&gt;False&lt;/code&gt;, and &lt;code&gt;None&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tempting fix is a chain of regular expressions. That works until a string&lt;br&gt;
contains punctuation that looks like syntax. A safer repair workflow is narrow,&lt;br&gt;
explainable, and followed by a real parse.&lt;/p&gt;
&lt;h2&gt;
  
  
  Keep the original
&lt;/h2&gt;

&lt;p&gt;Do not edit the only copy. Preserve the raw response or log fragment before&lt;br&gt;
changing anything.&lt;/p&gt;

&lt;p&gt;If the source includes prose or Markdown, first isolate the candidate JSON&lt;br&gt;
block. A repair tool should not guess which paragraph was intended to be data.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here is the result:

```json
{
  status: 'ok',
  retry: False,
  items: [1, 2, 3,],
}
```
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The data block is repairable. The surrounding sentence is not part of the&lt;br&gt;
document.&lt;/p&gt;
&lt;h2&gt;
  
  
  Apply known transformations
&lt;/h2&gt;

&lt;p&gt;Use a deterministic sequence and record every change:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Remove supported comments without touching comment-like text inside strings.&lt;/li&gt;
&lt;li&gt;Normalize &lt;code&gt;True&lt;/code&gt;, &lt;code&gt;False&lt;/code&gt;, and &lt;code&gt;None&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Quote bare object keys.&lt;/li&gt;
&lt;li&gt;Replace single quotes only when the structure is unambiguous.&lt;/li&gt;
&lt;li&gt;Remove trailing commas.&lt;/li&gt;
&lt;li&gt;Parse the result with a strict JSON parser.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are bounded scans, not blind replacements. Each rule skips quoted&lt;br&gt;
strings. Single-quoted bodies are decoded and then serialized again, so escaped&lt;br&gt;
apostrophes, bare double quotes, and &lt;code&gt;//&lt;/code&gt; inside URLs are not treated as syntax.&lt;/p&gt;

&lt;p&gt;The example can become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"retry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If two interpretations are possible, stop. A visible parse error is safer than&lt;br&gt;
a confident but invented repair.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate meaning after syntax
&lt;/h2&gt;

&lt;p&gt;Valid JSON can still be wrong.&lt;/p&gt;

&lt;p&gt;An AI response may omit a field, turn a number into a string, or stop halfway&lt;br&gt;
through an array. A log fragment may contain only one nested object from a&lt;br&gt;
larger payload.&lt;/p&gt;

&lt;p&gt;After the strict parse:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compare the result with a known-good example.&lt;/li&gt;
&lt;li&gt;Check important value types.&lt;/li&gt;
&lt;li&gt;Use a JSON Schema for API payloads.&lt;/li&gt;
&lt;li&gt;Use a tree diff for configuration changes.&lt;/li&gt;
&lt;li&gt;Keep the repair report with the result during review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Syntax repair answers "can this be parsed?" It does not answer "is this the&lt;br&gt;
right data?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep production payloads local
&lt;/h2&gt;

&lt;p&gt;Broken JSON often comes from places that contain customer data, internal IDs,&lt;br&gt;
or credentials. Sending it to a formatter creates another copy outside the&lt;br&gt;
system you are debugging.&lt;/p&gt;

&lt;p&gt;MonoTools JSON Repair runs in the current browser tab and shows the repair&lt;br&gt;
rules it applied:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://monoware.app/guides/repair-broken-json-safely?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=json_repair_guide" rel="noopener noreferrer"&gt;https://monoware.app/guides/repair-broken-json-safely?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=json_repair_guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The guide also links the next checks: strict formatting, JSON Tree Diff, and&lt;br&gt;
JSON Schema.&lt;/p&gt;

&lt;p&gt;Disclosure: MonoTools is my project. The repair tool intentionally leaves&lt;br&gt;
ambiguous input unresolved instead of pretending every invalid document has one&lt;br&gt;
obvious answer.&lt;/p&gt;

</description>
      <category>json</category>
      <category>javascript</category>
      <category>debugging</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Remove EXIF and GPS Metadata Before Sharing a Photo</title>
      <dc:creator>Tanya</dc:creator>
      <pubDate>Fri, 31 Jul 2026 06:04:44 +0000</pubDate>
      <link>https://dev.to/tanya_monoware/how-to-remove-exif-and-gps-metadata-before-sharing-a-photo-2c6i</link>
      <guid>https://dev.to/tanya_monoware/how-to-remove-exif-and-gps-metadata-before-sharing-a-photo-2c6i</guid>
      <description>&lt;p&gt;A JPEG can contain GPS coordinates, capture times, camera and lens details,&lt;br&gt;
editing software, comments, and copyright fields. That may be useful in a&lt;br&gt;
private archive, but risky in a public issue, marketplace listing, or social&lt;br&gt;
post.&lt;/p&gt;

&lt;p&gt;Do not assume the upload destination will remove it. Platforms process formats&lt;br&gt;
differently and can change behavior. Create a clean public copy before the file&lt;br&gt;
leaves your control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspect a sharing copy
&lt;/h2&gt;

&lt;p&gt;Keep the original private. On a separate copy, check for GPS data, timestamps,&lt;br&gt;
camera identifiers, and EXIF, XMP, or IPTC fields. The goal is not to damage&lt;br&gt;
your archive; it is to make a public file containing only what the destination&lt;br&gt;
needs.&lt;/p&gt;

&lt;p&gt;A practical workflow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inspect the sharing copy locally.&lt;/li&gt;
&lt;li&gt;Export a cleaned file.&lt;/li&gt;
&lt;li&gt;Inspect that exact output again.&lt;/li&gt;
&lt;li&gt;Review what is visible in the pixels.&lt;/li&gt;
&lt;li&gt;Rename it clearly before uploading.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The MonoTools EXIF Inspector &amp;amp; Cleaner does this in the current browser tab.&lt;br&gt;
The selected photo is read through browser APIs and is not uploaded to a&lt;br&gt;
processing server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand the format boundary
&lt;/h2&gt;

&lt;p&gt;JPEG stores metadata in APP and COM segments around its compressed image data.&lt;br&gt;
The cleaner can remove EXIF, XMP, Photoshop/IPTC data, comments, and optional&lt;br&gt;
unknown APP containers without recompressing pixels.&lt;/p&gt;

&lt;p&gt;PNG cleanup covers EXIF, XMP/text, and modification-time chunks. WebP cleanup&lt;br&gt;
covers EXIF and XMP chunks. All three exports keep their original format and&lt;br&gt;
are scanned again before the metadata check passes.&lt;/p&gt;

&lt;p&gt;ICC profiles stay by default because removing them can change color rendering&lt;br&gt;
in managed workflows. If you choose to remove one, compare the cleaned copy&lt;br&gt;
with the original when color accuracy matters.&lt;/p&gt;

&lt;p&gt;The tool can also copy or download a versioned JSON policy report. Metadata&lt;br&gt;
checks receive separate source and cleaned-output pass/fail results. Visible&lt;br&gt;
pixels remain a human gate, and raw metadata values are omitted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the pixels too
&lt;/h2&gt;

&lt;p&gt;Metadata removal cannot hide information already visible in a photo. Look for&lt;br&gt;
street signs, faces, badges, vehicle plates, screens, shipping labels, QR&lt;br&gt;
codes, landmarks, and reflections. Cropping, blurring, or replacing the image&lt;br&gt;
may still be necessary.&lt;/p&gt;

&lt;p&gt;If the public copy also needs resizing or format conversion, do that after the&lt;br&gt;
privacy review, then inspect the final file again. Compression and metadata&lt;br&gt;
cleanup solve different problems.&lt;/p&gt;

&lt;p&gt;I maintain MonoTools and wrote a fuller guide for its browser-local EXIF&lt;br&gt;
Inspector &amp;amp; Cleaner:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://monoware.app/guides/remove-exif-metadata-before-sharing?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=exif_cleanup_guide" rel="noopener noreferrer"&gt;https://monoware.app/guides/remove-exif-metadata-before-sharing?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=exif_cleanup_guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disclosure: MonoTools is my project.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>security</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Compress Images in the Browser Without Uploading Them</title>
      <dc:creator>Tanya</dc:creator>
      <pubDate>Fri, 31 Jul 2026 05:59:23 +0000</pubDate>
      <link>https://dev.to/tanya_monoware/how-to-compress-images-in-the-browser-without-uploading-them-4hc7</link>
      <guid>https://dev.to/tanya_monoware/how-to-compress-images-in-the-browser-without-uploading-them-4hc7</guid>
      <description>&lt;p&gt;A screenshot can be 4000 pixels wide even when a page renders it at 900&lt;br&gt;
pixels. Lowering the quality slider may help, but the better first question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is the largest size at which this image will actually be displayed?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Resize first
&lt;/h2&gt;

&lt;p&gt;If an image never renders wider than 1200 pixels, shipping a 4000-pixel source&lt;br&gt;
wastes download bytes and browser memory. Resize to the largest useful display&lt;br&gt;
width, preserve the aspect ratio, and do not upscale a smaller source.&lt;/p&gt;

&lt;p&gt;For a one-off asset, the whole process can stay in the browser:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Decode the selected file locally.&lt;/li&gt;
&lt;li&gt;Scale it to a maximum width.&lt;/li&gt;
&lt;li&gt;Draw it to a Canvas.&lt;/li&gt;
&lt;li&gt;Export with the browser's WebP, JPEG, or PNG encoder.&lt;/li&gt;
&lt;li&gt;Compare dimensions and file size, then inspect the preview.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is how the MonoTools Image Optimizer works. The selected file remains in&lt;br&gt;
the current tab instead of being sent to an image-processing service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose a format for the content
&lt;/h2&gt;

&lt;p&gt;WebP is a useful default for screenshots and mixed-content images. It supports&lt;br&gt;
transparency and often produces smaller files than JPEG or PNG.&lt;/p&gt;

&lt;p&gt;JPEG suits photographs when transparency is unnecessary. Reduce quality&lt;br&gt;
carefully around faces, text overlays, sharp UI edges, and gradients.&lt;/p&gt;

&lt;p&gt;PNG remains appropriate for lossless graphics and transparency. A browser's&lt;br&gt;
quality setting does not make PNG behave like lossy JPEG, so moving the slider&lt;br&gt;
may have little effect.&lt;/p&gt;

&lt;p&gt;Conversion alone is not optimization. Compare the actual output and view it&lt;br&gt;
near its intended page size. Small text, one-pixel borders, shadows, and logo&lt;br&gt;
edges tend to reveal damage first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Know the limits
&lt;/h2&gt;

&lt;p&gt;Local processing avoids an upload, but decoded images can consume far more&lt;br&gt;
memory than their compressed files. Very large sources may slow the tab or&lt;br&gt;
make Canvas export fail. Reduce the target width or process one image at a&lt;br&gt;
time when that happens.&lt;/p&gt;

&lt;p&gt;Browser encoders can also produce slightly different output. This workflow is&lt;br&gt;
good for individual assets and visual review; a versioned build dependency is&lt;br&gt;
better for large batches or reproducible production output.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;srcset&lt;/code&gt; or &lt;code&gt;picture&lt;/code&gt; when a page needs several responsive sizes. Also&lt;br&gt;
inspect GPS, camera, or timestamp metadata separately: resizing an image does&lt;br&gt;
not guarantee that private metadata is removed.&lt;/p&gt;

&lt;p&gt;I maintain MonoTools and wrote a fuller guide for its browser-local Image&lt;br&gt;
Optimizer:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://monoware.app/guides/compress-images-locally?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=local_image_compression" rel="noopener noreferrer"&gt;https://monoware.app/guides/compress-images-locally?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=local_image_compression&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disclosure: MonoTools is my project.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>javascript</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Inspect a JWT Without Uploading a Production Token</title>
      <dc:creator>Tanya</dc:creator>
      <pubDate>Thu, 30 Jul 2026 16:08:15 +0000</pubDate>
      <link>https://dev.to/tanya_monoware/how-to-inspect-a-jwt-without-uploading-a-production-token-4co6</link>
      <guid>https://dev.to/tanya_monoware/how-to-inspect-a-jwt-without-uploading-a-production-token-4co6</guid>
      <description>&lt;p&gt;A JSON Web Token often looks like opaque security data, but its header and&lt;br&gt;
payload are normally readable Base64URL segments. That makes local inspection&lt;br&gt;
straightforward. It also creates two common mistakes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uploading a live bearer token to an unknown server-side decoder.&lt;/li&gt;
&lt;li&gt;Treating decoded claims as proof that the token is authentic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This workflow separates three different jobs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inspecting the token structure.&lt;/li&gt;
&lt;li&gt;Checking claims such as issuer, audience and expiration.&lt;/li&gt;
&lt;li&gt;Verifying that a trusted issuer signed the token.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first two can be done locally in a browser tab. The third requires the&lt;br&gt;
expected algorithm and trusted key material.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Treat the JWT as a credential
&lt;/h2&gt;

&lt;p&gt;A JWT may be readable, but it can still grant access to an API. If it is a&lt;br&gt;
bearer token, anyone who receives the original compact token may be able to use&lt;br&gt;
it until it expires or is revoked.&lt;/p&gt;

&lt;p&gt;Before debugging, decide what kind of token you are handling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A fake or test token is usually safe to paste into examples.&lt;/li&gt;
&lt;li&gt;A staging token may still expose internal systems.&lt;/li&gt;
&lt;li&gt;A production token should not be copied into public tools, tickets or chat.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a production token has already crossed an untrusted boundary, redaction is&lt;br&gt;
not enough. Rotate or revoke it and treat the event as a credential exposure.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Split the compact token
&lt;/h2&gt;

&lt;p&gt;A typical JWT has three dot-separated segments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;header.payload.signature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The header and payload are Base64URL-encoded JSON. They are not encrypted by&lt;br&gt;
default. That means you can decode the first two segments locally and read the&lt;br&gt;
claims without sending the compact token to a server.&lt;/p&gt;

&lt;p&gt;The header usually includes fields such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RS256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"typ"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JWT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example-key-id"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The payload may include registered claims:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://issuer.example"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"api.example"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1764211200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nbf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1764207600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1764207600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"read:profile"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not paste a real token into an article, issue or support ticket. Use a&lt;br&gt;
synthetic example or share only the claim values needed to explain the failure.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Check the claims that commonly fail
&lt;/h2&gt;

&lt;p&gt;Most authentication debugging starts with a small set of fields.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;iss&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The issuer must match the identity provider or token service expected by the&lt;br&gt;
API. A common failure is using a token from the right environment but the wrong&lt;br&gt;
issuer configuration.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;aud&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The audience should identify the API or client that the token was issued for.&lt;br&gt;
If the token was minted for a different API, the claims may look correct while&lt;br&gt;
the request still fails authorization.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;exp&lt;/code&gt;, &lt;code&gt;nbf&lt;/code&gt; and &lt;code&gt;iat&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;JWT numeric date fields are seconds since the Unix epoch. Many bugs come from&lt;br&gt;
mixing seconds and milliseconds.&lt;/p&gt;

&lt;p&gt;Check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is &lt;code&gt;exp&lt;/code&gt; already in the past?&lt;/li&gt;
&lt;li&gt;Is &lt;code&gt;nbf&lt;/code&gt; still in the future?&lt;/li&gt;
&lt;li&gt;Does &lt;code&gt;iat&lt;/code&gt; make sense for the login or refresh event?&lt;/li&gt;
&lt;li&gt;Are the server and client clocks skewed?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Custom roles and scopes
&lt;/h3&gt;

&lt;p&gt;Applications often encode permissions in custom claims such as &lt;code&gt;scope&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;roles&lt;/code&gt;, &lt;code&gt;permissions&lt;/code&gt; or tenant-specific fields. These are useful for&lt;br&gt;
debugging, but they are only trustworthy after signature verification.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Decoding is not verification
&lt;/h2&gt;

&lt;p&gt;Anyone can construct a JWT-looking string with a header and payload. A decoder&lt;br&gt;
can tell you what the token claims. It cannot tell you whether the token was&lt;br&gt;
signed by the expected issuer.&lt;/p&gt;

&lt;p&gt;Verification needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The expected algorithm.&lt;/li&gt;
&lt;li&gt;The trusted signing secret or public key.&lt;/li&gt;
&lt;li&gt;Issuer and audience checks.&lt;/li&gt;
&lt;li&gt;Rejection of unexpected algorithms.&lt;/li&gt;
&lt;li&gt;Current time validation for &lt;code&gt;exp&lt;/code&gt; and &lt;code&gt;nbf&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For HS256 development tokens, the verifier needs the shared secret. For RS256,&lt;br&gt;
ES256 or production identity providers, the backend normally verifies against a&lt;br&gt;
published JWKS or a configured public key.&lt;/p&gt;

&lt;p&gt;Do not trust the &lt;code&gt;alg&lt;/code&gt; value from the token by itself. Your verification code&lt;br&gt;
should enforce the algorithm that the application expects.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Build a share-safe diagnostic
&lt;/h2&gt;

&lt;p&gt;The useful output of JWT debugging is usually not the original token. It is a&lt;br&gt;
small set of facts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Token structure: 3 JWT segments
Header alg: RS256
Header kid: example-key-id
Issuer: https://issuer.example
Audience: api.example
Subject: user_123
exp: 1764211200 / 2025-11-27T00:00:00Z
nbf: 1764207600 / 2025-11-26T23:00:00Z
Verification error: audience mismatch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before sharing the diagnostic:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Remove the original compact token.&lt;/li&gt;
&lt;li&gt;Remove unrelated cookies or authorization headers.&lt;/li&gt;
&lt;li&gt;Scan the remaining text for other credentials.&lt;/li&gt;
&lt;li&gt;Include the exact verification error and expected issuer or audience.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That gives the next person enough context to debug without receiving a reusable&lt;br&gt;
credential.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. A local workflow
&lt;/h2&gt;

&lt;p&gt;My preferred workflow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Decode the JWT header and payload locally.&lt;/li&gt;
&lt;li&gt;Convert &lt;code&gt;exp&lt;/code&gt;, &lt;code&gt;nbf&lt;/code&gt; and &lt;code&gt;iat&lt;/code&gt; to readable dates.&lt;/li&gt;
&lt;li&gt;Compare &lt;code&gt;iss&lt;/code&gt; and &lt;code&gt;aud&lt;/code&gt; with the API configuration.&lt;/li&gt;
&lt;li&gt;Verify the signature in backend code or a local lab with trusted key
material.&lt;/li&gt;
&lt;li&gt;Redact the original token before opening a ticket or asking for help.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I maintain MonoTools, including a browser-local JWT decoder and the longer&lt;br&gt;
canonical guide for this workflow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://monoware.app/guides/inspect-jwt-without-uploading?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=jwt_local_guide" rel="noopener noreferrer"&gt;https://monoware.app/guides/inspect-jwt-without-uploading?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=jwt_local_guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disclosure: MonoTools is my project. The guide and decoder are useful for local&lt;br&gt;
inspection, but signature verification still belongs in trusted verification&lt;br&gt;
code with the expected key material.&lt;/p&gt;

</description>
      <category>security</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>authentication</category>
    </item>
  </channel>
</rss>
