<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>PawaOps</title>
	<atom:link href="https://pawaops.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://pawaops.com</link>
	<description></description>
	<lastBuildDate>Wed, 17 Jun 2026 21:35:30 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://pawaops.com/wp-content/uploads/2026/01/cropped-Pawaops_icon-32x32.png</url>
	<title>PawaOps</title>
	<link>https://pawaops.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>PawaPass — Password Strength Checker &#038; Secret Generator</title>
		<link>https://pawaops.com/pawa-pass/</link>
					<comments>https://pawaops.com/pawa-pass/#respond</comments>
		
		<dc:creator><![CDATA[Admin Pawaops]]></dc:creator>
		<pubDate>Wed, 17 Jun 2026 21:06:04 +0000</pubDate>
				<category><![CDATA[Privacy & Security]]></category>
		<category><![CDATA[Basics]]></category>
		<category><![CDATA[Internet & Network]]></category>
		<category><![CDATA[Software]]></category>
		<guid isPermaLink="false">https://pawaops.com/?p=256</guid>

					<description><![CDATA[Générez des secrets forts et vérifiez la robustesse d’un mot de passe directement dans votre navigateur. PawaPass génère des mots de passe, passphrases, tokens et clés de style API. Il estime aussi la force d’un mot de passe, détecte les faiblesses évidentes et donne une estimation approximative du temps nécessaire pour le casser selon plusieurs ... <a title="PawaPass — Password Strength Checker &#38; Secret Generator" class="read-more" href="https://pawaops.com/pawa-pass/" aria-label="Read more about PawaPass — Password Strength Checker &#38; Secret Generator">Read more</a>]]></description>
										<content:encoded><![CDATA[
<article class="pawa-tool pawa-pass-tool">
 

  <p>
    <strong>Générez des secrets forts et vérifiez la robustesse d’un mot de passe directement dans votre navigateur.</strong>
  </p>

  <p>
    PawaPass génère des mots de passe, passphrases, tokens et clés de style API. Il estime aussi la force
    d’un mot de passe, détecte les faiblesses évidentes et donne une estimation approximative du temps nécessaire
    pour le casser selon plusieurs profils d’attaque.
  </p>

  <p lang="en">
    <strong>Generate strong secrets and check password strength directly in your browser.</strong>
  </p>

  <hr>

  <section id="pawa-pass-app">
    <h2>Generator</h2>

&#8220;`
<p>
  <label for="pawaPassType"><strong>Secret type</strong></label><br>
  <select id="pawaPassType">
    <option value="password">Strong password</option>
    <option value="passphrase">Readable passphrase</option>
    <option value="hex">Hex token</option>
    <option value="base64url">Base64URL token</option>
    <option value="apikey">API-style key</option>
  </select>
</p>

<p>
  <label for="pawaPassCount"><strong>How many</strong></label><br>
  <input id="pawaPassCount" type="number" min="1" max="20" value="3">
</p>

<fieldset>
  <legend><strong>Password options</strong></legend>

  <p>
    <label for="pawaPassLength">Length</label><br>
    <input id="pawaPassLength" type="number" min="8" max="128" value="24">
  </p>

  <label><input type="checkbox" id="pawaPassLower" checked> Lowercase</label><br>
  <label><input type="checkbox" id="pawaPassUpper" checked> Uppercase</label><br>
  <label><input type="checkbox" id="pawaPassNumbers" checked> Numbers</label><br>
  <label><input type="checkbox" id="pawaPassSymbols" checked> Symbols</label><br>
  <label><input type="checkbox" id="pawaPassNoAmbiguous" checked> Avoid ambiguous chars: 0 O o 1 l I</label>
</fieldset>

<fieldset>
  <legend><strong>Passphrase options</strong></legend>

  <p>
    <label for="pawaPassWords">Words</label><br>
    <input id="pawaPassWords" type="number" min="3" max="12" value="5">
  </p>

  <p>
    <label for="pawaPassSeparator">Separator</label><br>
    <input id="pawaPassSeparator" type="text" value="-" maxlength="4">
  </p>

  <label><input type="checkbox" id="pawaPassCapitalize"> Capitalize words</label><br>
  <label><input type="checkbox" id="pawaPassAddNumber" checked> Add random number</label>
</fieldset>

<fieldset>
  <legend><strong>Token options</strong></legend>

  <p>
    <label for="pawaPassBytes">Random bytes</label><br>
    <input id="pawaPassBytes" type="number" min="8" max="128" value="32">
  </p>

  <p>
    <label for="pawaPassPrefix">API key prefix</label><br>
    <input id="pawaPassPrefix" type="text" value="pawa" placeholder="pawa">
  </p>
</fieldset>

<p>
  <button type="button" id="pawaPassGenerate">Generate</button>
  <button type="button" id="pawaPassToggleGenerated">Show generated secret(s)</button>
  <button type="button" id="pawaPassCopyGenerated">Copy generated secret(s)</button>
  <button type="button" id="pawaPassUseFirst">Check first generated secret</button>
  <button type="button" id="pawaPassClearGenerated">Clear generated</button>
</p>

<div id="pawaPassError" role="alert"></div>

<h3>Generated secret(s)</h3>
<textarea id="pawaPassGeneratedOutput" rows="7" readonly placeholder="Generated secrets are hidden by default."></textarea>

<hr>

<h2>Password checker</h2>

<p>
  <label for="pawaPassCheckInput"><strong>Password to check</strong></label><br>
  <input
    id="pawaPassCheckInput"
    type="password"
    autocomplete="new-password"
    autocapitalize="off"
    autocorrect="off"
    spellcheck="false"
    placeholder="Paste or type a password">
</p>

<p>
  <button type="button" id="pawaPassToggleCheck">Show password</button>
  <button type="button" id="pawaPassCheck">Check strength</button>
  <button type="button" id="pawaPassClearCheck">Clear checked password</button>
  <button type="button" id="pawaPassCopyReport">Copy report</button>
  <a href="/pawa-seal/">Open PawaSeal</a>
</p>

<h3>Strength summary</h3>
<pre id="pawaPassStrength">Loading generator...</pre>

<h3>Crack-time estimate</h3>
<div id="pawaPassCrackTable"></div>

<h3>Findings</h3>
<div id="pawaPassFindings"></div>

<h3>Report</h3>
<pre id="pawaPassReport">No report yet.</pre>
&#8220;`

  </section>

  <hr>

  <section class="pawa-article-content">
    <h2>About PawaPass</h2>

&#8220;`
<p>
  PawaPass est un outil local-first pour générer et évaluer des secrets. Les mots de passe générés ou collés
  sont masqués par défaut afin d’éviter les fuites visuelles pendant un partage d’écran, une capture ou une
  intervention support.
</p>

<p>
  Le générateur utilise l’API cryptographique du navigateur pour produire des valeurs aléatoires. Le checker
  estime la robustesse à partir de la longueur, du jeu de caractères, de patterns évidents et de pénalités simples.
  L’estimation du temps de cassage reste volontairement approximative : une vraie attaque dépend du type de hash,
  du matériel, des dictionnaires, des fuites existantes et de la qualité du stockage côté application.
</p>

<p>
  Un bon secret n’est pas seulement une chaîne complexe. Il doit être stocké correctement, transmis prudemment,
  limité dans le temps si possible, révoqué après usage et jamais collé en clair dans un ticket ou un chat.
</p>

<p lang="en">
  PawaPass is a local-first password and secret generator with a practical strength checker. It helps generate
  strong secrets, estimate resistance and avoid obvious operational mistakes.
</p>
&#8220;`

  </section>
</article>

<script>
(function () {
  function ready(fn) {
    if (document.readyState !== 'loading') fn();
    else document.addEventListener('DOMContentLoaded', fn);
  }

  ready(function () {
    var $ = function (id) { return document.getElementById(id); };

    var typeEl = $('pawaPassType');
    var countEl = $('pawaPassCount');
    var lengthEl = $('pawaPassLength');
    var lowerEl = $('pawaPassLower');
    var upperEl = $('pawaPassUpper');
    var numbersEl = $('pawaPassNumbers');
    var symbolsEl = $('pawaPassSymbols');
    var noAmbiguousEl = $('pawaPassNoAmbiguous');

    var wordsEl = $('pawaPassWords');
    var separatorEl = $('pawaPassSeparator');
    var capitalizeEl = $('pawaPassCapitalize');
    var addNumberEl = $('pawaPassAddNumber');

    var bytesEl = $('pawaPassBytes');
    var prefixEl = $('pawaPassPrefix');

    var generateBtn = $('pawaPassGenerate');
    var toggleGeneratedBtn = $('pawaPassToggleGenerated');
    var copyGeneratedBtn = $('pawaPassCopyGenerated');
    var useFirstBtn = $('pawaPassUseFirst');
    var clearGeneratedBtn = $('pawaPassClearGenerated');

    var checkInputEl = $('pawaPassCheckInput');
    var toggleCheckBtn = $('pawaPassToggleCheck');
    var checkBtn = $('pawaPassCheck');
    var clearCheckBtn = $('pawaPassClearCheck');
    var copyReportBtn = $('pawaPassCopyReport');

    var outputEl = $('pawaPassGeneratedOutput');
    var strengthEl = $('pawaPassStrength');
    var crackTableEl = $('pawaPassCrackTable');
    var findingsEl = $('pawaPassFindings');
    var reportEl = $('pawaPassReport');
    var errorEl = $('pawaPassError');

    if (!generateBtn || !checkBtn || !outputEl) return;

    var generatedSecrets = [];
    var generatedVisible = false;
    var lastReport = '';

    var wordList = [
      'anchor','apricot','atlas','aurora','bamboo','beacon','binary','bison','blossom','bridge',
      'canyon','carbon','cedar','cipher','cobalt','comet','copper','coral','delta','desert',
      'domain','drift','ember','engine','falcon','field','forest','frost','galaxy','garden',
      'harbor','hazel','helium','horizon','island','jungle','kernel','lagoon','lantern','laptop',
      'liberty','magnet','matrix','meadow','meteor','mirror','nebula','network','november','oasis',
      'object','ocean','orbit','packet','panda','pebble','phoenix','planet','plasma','prairie',
      'quartz','radar','random','raven','river','rocket','router','saffron','signal','silver',
      'socket','solar','spruce','stone','summit','syntax','tango','temple','thunder','tiger',
      'token','tundra','vector','velvet','violet','voyage','walnut','window','winter','yellow',
      'zephyr','zero','zinc','admin','backup','buffer','cache','cluster','console','daemon',
      'deploy','docker','gateway','gitlab','grafana','ingress','lambda','monitor','nginx','proxy',
      'queue','server','syslog','terminal','ubuntu','vault','worker','yaml','zone'
    ];

    var commonPasswords = [
      'password','password1','admin','administrator','root','toor','welcome','welcome1',
      'qwerty','azerty','123456','12345678','123456789','1234567890','000000','111111',
      'letmein','secret','default','changeme','passw0rd','p@ssw0rd','iloveyou','bonjour','motdepasse'
    ];

    var attackerProfiles = [
      { name: 'Online login, rate-limited', gps: 0.1, note: 'About one try every 10 seconds.' },
      { name: 'Laptop, slow password hash', gps: 100, note: 'bcrypt / Argon2id-style defensive scenario.' },
      { name: 'Gaming GPU, fast hash', gps: 1000000000, note: 'Fast hash offline attack, simplified.' },
      { name: 'Small GPU rig, fast hash', gps: 50000000000, note: 'Several GPUs against a fast hash.' },
      { name: 'Serious cracking rig, fast hash', gps: 1000000000000, note: 'Illustrative high-end offline scenario.' }
    ];

    function showError(message) {
      errorEl.textContent = message || '';
    }

    function clearError() {
      errorEl.textContent = '';
    }

    function escapeHtml(value) {
      return String(value == null ? '' : value)
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
    }

    function secureRandomAvailable() {
      return !!(window.crypto && window.crypto.getRandomValues);
    }

    function cryptoBytes(length) {
      if (!secureRandomAvailable()) {
        throw new Error('Crypto API unavailable. Use HTTPS and a modern browser.');
      }

      var bytes = new Uint8Array(length);
      window.crypto.getRandomValues(bytes);
      return bytes;
    }

    function randomInt(max) {
      if (!secureRandomAvailable()) {
        throw new Error('Crypto API unavailable. Use HTTPS and a modern browser.');
      }

      var maxUint32 = 0xffffffff;
      var limit = maxUint32 - (maxUint32 % max);
      var buffer = new Uint32Array(1);

      do {
        window.crypto.getRandomValues(buffer);
      } while (buffer[0] >= limit);

      return buffer[0] % max;
    }

    function pickOne(chars) {
      return chars.charAt(randomInt(chars.length));
    }

    function shuffle(array) {
      for (var i = array.length - 1; i > 0; i--) {
        var j = randomInt(i + 1);
        var tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
      }

      return array;
    }

    function sanitize(chars) {
      if (!noAmbiguousEl.checked) return chars;
      return chars.replace(/[0Oo1lI]/g, '');
    }

    function generatePassword() {
      var length = Number(lengthEl.value || 24);
      var sets = [];

      if (lowerEl.checked) sets.push(sanitize('abcdefghijklmnopqrstuvwxyz'));
      if (upperEl.checked) sets.push(sanitize('ABCDEFGHIJKLMNOPQRSTUVWXYZ'));
      if (numbersEl.checked) sets.push(sanitize('0123456789'));
      if (symbolsEl.checked) sets.push('!@#$%^&*()-_=+[]{};:,.?/');

      sets = sets.filter(function (s) { return s.length > 0; });

      if (!sets.length) throw new Error('Select at least one character set.');
      if (length < sets.length) length = sets.length;

      var all = sets.join('');
      var chars = [];

      sets.forEach(function (set) {
        chars.push(pickOne(set));
      });

      while (chars.length < length) {
        chars.push(pickOne(all));
      }

      return shuffle(chars).join('');
    }

    function generatePassphrase() {
      var count = Math.max(3, Math.min(12, Number(wordsEl.value || 5)));
      var separator = separatorEl.value || '-';
      var words = [];

      for (var i = 0; i < count; i++) {
        var word = wordList[randomInt(wordList.length)];

        if (capitalizeEl.checked) {
          word = word.charAt(0).toUpperCase() + word.slice(1);
        }

        words.push(word);
      }

      if (addNumberEl.checked) {
        words.push(String(10 + randomInt(90)));
      }

      return words.join(separator);
    }

    function bytesToHex(bytes) {
      var out = '';

      for (var i = 0; i < bytes.length; i++) {
        out += bytes[i].toString(16).padStart(2, '0');
      }

      return out;
    }

    function bytesToBase64Url(bytes) {
      var binary = '';

      for (var i = 0; i < bytes.length; i++) {
        binary += String.fromCharCode(bytes[i]);
      }

      return btoa(binary)
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
    }

    function cleanPrefix(value) {
      var cleaned = String(value || 'pawa')
        .toLowerCase()
        .replace(/[^a-z0-9_]/g, '')
        .replace(/^_+|_+$/g, '');

      return cleaned || 'pawa';
    }

    function generateApiKey() {
      var bytes = Math.max(8, Math.min(128, Number(bytesEl.value || 32)));
      return cleanPrefix(prefixEl.value) + '_live_' + bytesToBase64Url(cryptoBytes(bytes));
    }

    function generateOne() {
      var type = typeEl.value;
      var bytes = Math.max(8, Math.min(128, Number(bytesEl.value || 32)));

      if (type === 'password') return generatePassword();
      if (type === 'passphrase') return generatePassphrase();
      if (type === 'hex') return bytesToHex(cryptoBytes(bytes));
      if (type === 'base64url') return bytesToBase64Url(cryptoBytes(bytes));
      if (type === 'apikey') return generateApiKey();

      throw new Error('Unknown secret type.');
    }

    function maskSecret(secret) {
      return '•'.repeat(Math.min(Math.max(secret.length, 8), 80));
    }

    function renderGenerated() {
      if (!generatedSecrets.length) {
        outputEl.value = '';
        return;
      }

      if (generatedVisible) {
        outputEl.value = generatedSecrets.join('\n');
      } else {
        outputEl.value = generatedSecrets.map(function (secret, index) {
          return String(index + 1) + '. ' + maskSecret(secret);
        }).join('\n');
      }
    }

    function generateSecrets() {
      clearError();

      try {
        var count = Math.max(1, Math.min(20, Number(countEl.value || 1)));
        var results = [];

        for (var i = 0; i < count; i++) {
          results.push(generateOne());
        }

        generatedSecrets = results;
        generatedVisible = false;
        toggleGeneratedBtn.textContent = 'Show generated secret(s)';
        renderGenerated();

        checkInputEl.value = generatedSecrets[0] || '';
        checkInputEl.type = 'password';
        toggleCheckBtn.textContent = 'Show password';

        checkPassword();
      } catch (error) {
        showError(error.message);
      }
    }

    function charsetSize(password) {
      var size = 0;

      if (/[a-z]/.test(password)) size += 26;
      if (/[A-Z]/.test(password)) size += 26;
      if (/[0-9]/.test(password)) size += 10;
      if (/[^a-zA-Z0-9]/.test(password)) size += 33;

      return Math.max(size, 1);
    }

    function repeatedRun(password) {
      var max = 1;
      var current = 1;

      for (var i = 1; i < password.length; i++) {
        if (password[i] === password[i - 1]) {
          current++;
          if (current > max) max = current;
        } else {
          current = 1;
        }
      }

      return max;
    }

    function hasSequence(password) {
      var lower = password.toLowerCase();
      var sequences = [
        'abcdefghijklmnopqrstuvwxyz',
        'zyxwvutsrqponmlkjihgfedcba',
        '0123456789',
        '9876543210',
        'azertyuiop',
        'poiuytreza',
        'qwertyuiop',
        'poiuytrewq'
      ];

      for (var s = 0; s < sequences.length; s++) {
        var seq = sequences[s];

        for (var i = 0; i <= seq.length - 4; i++) {
          if (lower.indexOf(seq.slice(i, i + 4)) !== -1) return true;
        }
      }

      return false;
    }

    function detectFindings(password) {
      var findings = [];
      var lower = password.toLowerCase();

      if (!password) {
        findings.push(['bad', 'No password provided.']);
        return findings;
      }

      if (password.length < 8) findings.push(['bad', 'Very short password.']);
      else if (password.length < 12) findings.push(['warn', 'Short password. 12+ characters is a safer baseline.']);
      else if (password.length < 15) findings.push(['warn', 'Acceptable length, but 15+ is stronger.']);
      else findings.push(['good', 'Good length.']);

      if (commonPasswords.indexOf(lower) !== -1) findings.push(['bad', 'Common password detected.']);
      if (/(qwerty|azerty|asdf|zxcv|1234|2345|abcd|admin|root|test|demo)/i.test(password)) findings.push(['bad', 'Obvious pattern detected.']);
      if (hasSequence(password)) findings.push(['warn', 'Sequential pattern detected.']);
      if (repeatedRun(password) >= 4) findings.push(['warn', 'Repeated character run detected.']);
      if (/(19|20)\d{2}/.test(password)) findings.push(['warn', 'Year-like pattern detected.']);
      if (/^\d+$/.test(password)) findings.push(['bad', 'Numbers only.']);
      if (/^[a-zA-Z]+\d{1,4}$/.test(password)) findings.push(['warn', 'Word followed by short number detected.']);

      if (!/[a-z]/.test(password)) findings.push(['info', 'No lowercase letter.']);
      if (!/[A-Z]/.test(password)) findings.push(['info', 'No uppercase letter.']);
      if (!/[0-9]/.test(password)) findings.push(['info', 'No digit.']);
      if (!/[^a-zA-Z0-9]/.test(password)) findings.push(['info', 'No symbol.']);

      return findings;
    }

    function estimateEntropy(password, findings) {
      if (!password) return 0;

      var naive = password.length * Math.log2(charsetSize(password));
      var penalty = 0;

      findings.forEach(function (f) {
        if (f[0] === 'bad') penalty += 25;
        if (f[0] === 'warn') penalty += 10;
      });

      if (commonPasswords.indexOf(password.toLowerCase()) !== -1) penalty += 80;

      return Math.max(0, naive - penalty);
    }

    function strengthLabel(bits, findings) {
      var hasBad = findings.some(function (f) { return f[0] === 'bad'; });

      if (hasBad && bits < 80) return 'Weak';
      if (bits < 40) return 'Very weak';
      if (bits < 60) return 'Weak';
      if (bits < 80) return 'Moderate';
      if (bits < 112) return 'Strong';
      if (bits < 160) return 'Very strong';
      return 'Excellent for practical use';
    }

    function formatDuration(seconds) {
      if (!isFinite(seconds)) return 'effectively impossible in this model';
      if (seconds < 1) return 'less than 1 second';

      var units = [
        ['year', 31536000],
        ['day', 86400],
        ['hour', 3600],
        ['minute', 60],
        ['second', 1]
      ];

      var parts = [];

      for (var i = 0; i < units.length; i++) {
        var value = Math.floor(seconds / units[i][1]);

        if (value > 0) {
          parts.push(value + ' ' + units[i][0] + (value > 1 ? 's' : ''));
          seconds -= value * units[i][1];
        }

        if (parts.length === 2) break;
      }

      return parts.join(', ') || 'less than 1 second';
    }

    function crackSeconds(bits, gps) {
      return Math.pow(2, Math.min(bits, 1024)) / 2 / gps;
    }

    function renderCrackTable(bits) {
      var html = '<table><thead><tr><th>Attacker profile</th><th>Guesses/sec</th><th>Average estimate</th><th>Note</th></tr></thead><tbody>';

      attackerProfiles.forEach(function (p) {
        html += '<tr>';
        html += '<td>' + escapeHtml(p.name) + '</td>';
        html += '<td>' + escapeHtml(p.gps.toLocaleString()) + '</td>';
        html += '<td>' + escapeHtml(formatDuration(crackSeconds(bits, p.gps))) + '</td>';
        html += '<td>' + escapeHtml(p.note) + '</td>';
        html += '</tr>';
      });

      html += '</tbody></table>';
      html += '<p><strong>Note:</strong> rough estimate. Real attacks use leaks, dictionaries, rules and patterns.</p>';

      crackTableEl.innerHTML = html;
    }

    function renderFindings(findings) {
      var html = '<ul>';

      findings.forEach(function (f) {
        html += '<li><strong>' + escapeHtml(f[0].toUpperCase()) + ':</strong> ' + escapeHtml(f[1]) + '</li>';
      });

      html += '</ul>';
      findingsEl.innerHTML = html;
    }

    function checkPassword() {
      var password = checkInputEl.value || '';
      var findings = detectFindings(password);
      var bits = estimateEntropy(password, findings);
      var label = strengthLabel(bits, findings);
      var pool = charsetSize(password);

      strengthEl.textContent = [
        'Length: ' + password.length + ' characters',
        'Detected character pool: about ' + pool + ' characters',
        'Estimated entropy after penalties: ' + bits.toFixed(1) + ' bits',
        'Strength: ' + label,
        '',
        'Local estimate. The password is not intentionally sent by this tool.'
      ].join('\n');

      renderCrackTable(bits);
      renderFindings(findings);

      lastReport = [
        'PawaPass strength report',
        '',
        'Length: ' + password.length + ' characters',
        'Character pool: about ' + pool,
        'Estimated entropy: ' + bits.toFixed(1) + ' bits',
        'Strength: ' + label,
        '',
        'Findings:',
        findings.map(function (f) {
          return '- ' + f[0].toUpperCase() + ': ' + f[1];
        }).join('\n'),
        '',
        'Crack-time estimates:',
        attackerProfiles.map(function (p) {
          return '- ' + p.name + ': ' + formatDuration(crackSeconds(bits, p.gps)) + ' at ~' + p.gps.toLocaleString() + ' guesses/sec';
        }).join('\n'),
        '',
        'Notes:',
        '- Approximation, not a guarantee.',
        '- Real attacks use dictionaries, leaked lists and pattern rules.',
        '- Fast hashes are much easier to attack offline than slow password hashes.',
        '- Do not include the password itself in reports or tickets.'
      ].join('\n');

      reportEl.textContent = lastReport;
    }

    function copyText(value, button, label) {
      if (!value) return;

      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(value).then(function () {
          button.textContent = 'Copied';
          setTimeout(function () { button.textContent = label; }, 1200);
        });
      } else {
        var temp = document.createElement('textarea');
        temp.value = value;
        document.body.appendChild(temp);
        temp.select();
        document.execCommand('copy');
        document.body.removeChild(temp);
        button.textContent = 'Copied';
        setTimeout(function () { button.textContent = label; }, 1200);
      }
    }

    generateBtn.addEventListener('click', generateSecrets);

    toggleGeneratedBtn.addEventListener('click', function () {
      generatedVisible = !generatedVisible;
      toggleGeneratedBtn.textContent = generatedVisible ? 'Hide generated secret(s)' : 'Show generated secret(s)';
      renderGenerated();
    });

    copyGeneratedBtn.addEventListener('click', function () {
      copyText(generatedSecrets.join('\n'), copyGeneratedBtn, 'Copy generated secret(s)');
    });

    useFirstBtn.addEventListener('click', function () {
      if (!generatedSecrets.length) generateSecrets();

      checkInputEl.value = generatedSecrets[0] || '';
      checkInputEl.type = 'password';
      toggleCheckBtn.textContent = 'Show password';
      checkPassword();
    });

    clearGeneratedBtn.addEventListener('click', function () {
      generatedSecrets = [];
      generatedVisible = false;
      renderGenerated();
    });

    toggleCheckBtn.addEventListener('click', function () {
      if (checkInputEl.type === 'password') {
        checkInputEl.type = 'text';
        toggleCheckBtn.textContent = 'Hide password';
      } else {
        checkInputEl.type = 'password';
        toggleCheckBtn.textContent = 'Show password';
      }
    });

    checkBtn.addEventListener('click', checkPassword);

    clearCheckBtn.addEventListener('click', function () {
      checkInputEl.value = '';
      checkInputEl.type = 'password';
      toggleCheckBtn.textContent = 'Show password';
      strengthEl.textContent = 'Paste or generate a password, then click “Check strength”.';
      crackTableEl.innerHTML = '';
      findingsEl.innerHTML = '';
      reportEl.textContent = 'No report yet.';
      lastReport = '';
    });

    copyReportBtn.addEventListener('click', function () {
      copyText(lastReport || reportEl.textContent, copyReportBtn, 'Copy report');
    });

    checkInputEl.addEventListener('input', checkPassword);

    [
      typeEl, countEl, lengthEl, lowerEl, upperEl, numbersEl, symbolsEl, noAmbiguousEl,
      wordsEl, separatorEl, capitalizeEl, addNumberEl, bytesEl, prefixEl
    ].forEach(function (el) {
      if (!el) return;

      el.addEventListener('change', generateSecrets);

      if (el === separatorEl || el === prefixEl) {
        el.addEventListener('input', generateSecrets);
      }
    });

    generateSecrets();
  });
})();
</script>

]]></content:encoded>
					
					<wfw:commentRss>https://pawaops.com/pawa-pass/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>PawaDNS — DNS Helper</title>
		<link>https://pawaops.com/pawa-dns/</link>
					<comments>https://pawaops.com/pawa-dns/#respond</comments>
		
		<dc:creator><![CDATA[Admin Pawaops]]></dc:creator>
		<pubDate>Wed, 17 Jun 2026 20:49:20 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://pawaops.com/?p=253</guid>

					<description><![CDATA[Analysez rapidement les enregistrements DNS d’un domaine et obtenez des pistes de correction exploitables. PawaDNS interroge les principaux enregistrements DNS d’un domaine, résume les résultats, vérifie les bases web et email, puis génère des “Fix Cards” utiles pour administrateurs systèmes, DevOps, freelances web et équipes support. Analyze DNS records quickly and get practical fix cards. ... <a title="PawaDNS — DNS Helper" class="read-more" href="https://pawaops.com/pawa-dns/" aria-label="Read more about PawaDNS — DNS Helper">Read more</a>]]></description>
										<content:encoded><![CDATA[
<article class="pawa-tool pawa-dns-tool">

  <p>
    <strong>Analysez rapidement les enregistrements DNS d’un domaine et obtenez des pistes de correction exploitables.</strong>
  </p>

  <p>
    PawaDNS interroge les principaux enregistrements DNS d’un domaine, résume les résultats, vérifie les bases web
    et email, puis génère des “Fix Cards” utiles pour administrateurs systèmes, DevOps, freelances web et équipes support.
  </p>

  <p lang="en">
    <strong>Analyze DNS records quickly and get practical fix cards.</strong>
  </p>

  <p lang="en">
    PawaDNS queries common DNS records for a domain, summarizes results, checks web and email basics, and generates
    practical fix cards for sysadmins, DevOps engineers, web freelancers and support teams.
  </p>

  <hr>

  <section id="pawa-dns-app">
    <h2>Outil / Tool</h2>

&#8220;`
<p>
  <label for="pawaDnsDomain"><strong>Domain</strong></label><br>
  <input id="pawaDnsDomain" type="text" placeholder="example.com">
</p>

<p>
  <label for="pawaDnsSelectors"><strong>DKIM selectors to test, optional</strong></label><br>
  <input id="pawaDnsSelectors" type="text" value="default,google,selector1,selector2,k1" placeholder="default,google,selector1">
</p>

<p>
  <button type="button" id="pawaDnsAnalyze">Analyze DNS</button>
  <button type="button" id="pawaDnsCopyReport">Copy report</button>
  <button type="button" id="pawaDnsCopyCommands">Copy dig commands</button>
  <button type="button" id="pawaDnsReset">Reset</button>
</p>

<div id="pawaDnsError" role="alert"></div>

<h3>Summary</h3>
<pre id="pawaDnsSummary">Enter a domain and click “Analyze DNS”.</pre>

<h3>Records overview</h3>
<div id="pawaDnsOverview"></div>

<h3>Answers</h3>
<div id="pawaDnsAnswers"></div>

<h3>Mail security</h3>
<div id="pawaDnsMail"></div>

<h3>Fix Cards</h3>
<div id="pawaDnsFixCards"></div>

<h3>Useful commands</h3>
<pre><code id="pawaDnsCommands"></code></pre>

<h3>Raw DoH data</h3>
<details>
  <summary>Show raw JSON</summary>
  <pre><code id="pawaDnsRaw"></code></pre>
</details>
&#8220;`

  </section>

  <hr>

  <section class="pawa-article-content">
    <h2>Pourquoi PawaDNS est utile</h2>

&#8220;`
<p>
  Le DNS est une configuration critique. Lorsqu’il est incomplet ou incohérent, les symptômes sont très variés :
  site inaccessible, email qui tombe en spam, domaine qui pointe au mauvais serveur, certificat impossible à émettre,
  migration confuse ou propagation mal comprise.
</p>

<p>
  PawaDNS est conçu comme un outil opérationnel. Il ne se contente pas d’afficher des enregistrements bruts :
  il cherche à transformer les signaux importants en informations lisibles et en actions vérifiables.
</p>

<h2>Ce que l’outil vérifie</h2>

<ul>
  <li><code>A</code> : adresse IPv4 du domaine.</li>
  <li><code>AAAA</code> : adresse IPv6 du domaine.</li>
  <li><code>CNAME</code> : alias DNS.</li>
  <li><code>MX</code> : serveurs de réception email.</li>
  <li><code>TXT</code> : SPF, vérifications de domaine et autres données texte.</li>
  <li><code>NS</code> : serveurs de noms autoritaires.</li>
  <li><code>SOA</code> : informations de zone DNS.</li>
  <li><code>CAA</code> : autorités de certification autorisées.</li>
  <li><code>HTTPS</code> : informations modernes de service HTTPS quand présentes.</li>
  <li><code>_dmarc</code> : politique DMARC du domaine.</li>
  <li><code>DKIM</code> : test de sélecteurs DKIM courants ou fournis.</li>
</ul>

<h2>DNS web : A, AAAA et CNAME</h2>

<p>
  Pour qu’un domaine pointe vers un site, il a généralement besoin d’un enregistrement <code>A</code>, d’un
  enregistrement <code>AAAA</code>, d’un <code>CNAME</code>, ou d’une combinaison adaptée à l’hébergeur.
</p>

<p>
  Une erreur fréquente consiste à mettre à jour <code>www</code> mais pas le domaine racine, ou inversement.
  Une autre erreur fréquente est de laisser une vieille adresse IPv6 active alors que le serveur ne répond plus
  correctement dessus.
</p>

<h2>DNS email : MX, SPF, DKIM et DMARC</h2>

<p>
  Les problèmes email viennent souvent d’une configuration DNS incomplète.
</p>

<h3>MX</h3>

<p>
  Les enregistrements <code>MX</code> indiquent quels serveurs reçoivent les emails pour le domaine.
</p>

<h3>SPF</h3>

<p>
  SPF indique quels serveurs sont autorisés à envoyer des emails pour le domaine.
</p>

<pre><code>v=spf1 include:_spf.example.net -all</code></pre>

<p>
  Un domaine ne doit normalement pas avoir plusieurs enregistrements SPF séparés. C’est une erreur fréquente
  lorsqu’on ajoute plusieurs prestataires email.
</p>

<h3>DKIM</h3>

<p>
  DKIM publie une clé dans le DNS pour permettre la vérification cryptographique des emails signés.
  Le sélecteur dépend du fournisseur email. C’est pour cela que PawaDNS permet de tester plusieurs sélecteurs.
</p>

<pre><code>selector._domainkey.example.com TXT "v=DKIM1; k=rsa; p=..."</code></pre>

<h3>DMARC</h3>

<p>
  DMARC définit ce qu’il faut faire lorsqu’un email échoue aux contrôles SPF ou DKIM alignés.
</p>

<pre><code>_dmarc.example.com TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.com"</code></pre>

<p>
  Une politique <code>p=none</code> permet d’observer. <code>quarantine</code> et <code>reject</code> sont plus
  strictes et doivent être déployées progressivement.
</p>

<h2>CAA et certificats TLS</h2>

<p>
  L’enregistrement <code>CAA</code> indique quelles autorités de certification sont autorisées à émettre des
  certificats pour le domaine.
</p>

<pre><code>example.com CAA 0 issue "letsencrypt.org"</code></pre>

<p>
  L’absence de CAA n’est pas toujours une erreur, mais sa présence peut aider à mieux contrôler l’émission
  de certificats.
</p>

<h2>TTL et propagation DNS</h2>

<p>
  Le TTL indique combien de temps une réponse DNS peut être mise en cache. Lors d’une migration, il est souvent
  utile de réduire le TTL avant la bascule, puis de le remonter après stabilisation.
</p>

<p>
  Beaucoup de “problèmes de propagation” sont en réalité des effets normaux de cache DNS.
</p>

<h2>Fix Cards</h2>

<p>
  La partie la plus utile de PawaDNS est la génération de cartes d’action. Une Fix Card doit répondre à quatre
  questions :
</p>

<ul>
  <li>Quel est le problème ?</li>
  <li>Pourquoi c’est important ?</li>
  <li>Quelle est la prochaine vérification ?</li>
  <li>Quelle correction de départ peut être envisagée ?</li>
</ul>

<p>
  Exemple :
</p>

<pre><code>Problem:
```

DMARC record is missing.

Impact:
The domain has weaker protection against email spoofing.

Suggested start:
_dmarc.example.com TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.com"

Priority:
Medium to high if the domain sends email.</code></pre>

&#8220;`
<h2>Commandes utiles</h2>

<p>
  PawaDNS génère aussi des commandes <code>dig</code> pour reproduire les vérifications en terminal :
</p>

<pre><code>dig example.com A
```

dig example.com AAAA
dig example.com MX
dig example.com TXT
dig _dmarc.example.com TXT
dig example.com CAA</code></pre>

&#8220;`
<p>
  Pour comparer les réponses de résolveurs publics :
</p>

<pre><code>dig @1.1.1.1 example.com A
```

dig @8.8.8.8 example.com A</code></pre>

&#8220;`
<h2>Position d’administrateur systèmes</h2>

<p>
  Le DNS doit être traité comme une configuration de production. Un domaine peut sembler fonctionner tout en ayant
  des signaux faibles : IPv6 cassé, SPF incomplet, DMARC absent, ancien MX, CAA manquant, mauvais TTL ou enregistrement
  hérité d’un ancien prestataire.
</p>

<p>
  PawaDNS aide à lire ces signaux et à produire des vérifications concrètes. La correction finale dépend toujours
  du fournisseur DNS, de l’hébergeur, du prestataire email et du contexte de production.
</p>

<p lang="en">
  DNS should be treated as production configuration. PawaDNS helps read DNS signals and turn them into practical checks.
</p>
&#8220;`

  </section>
</article>

<script>
(function () {
  const domainEl = document.getElementById('pawaDnsDomain');
  const selectorsEl = document.getElementById('pawaDnsSelectors');
  const analyzeBtn = document.getElementById('pawaDnsAnalyze');
  const copyReportBtn = document.getElementById('pawaDnsCopyReport');
  const copyCommandsBtn = document.getElementById('pawaDnsCopyCommands');
  const resetBtn = document.getElementById('pawaDnsReset');

  const errorEl = document.getElementById('pawaDnsError');
  const summaryEl = document.getElementById('pawaDnsSummary');
  const overviewEl = document.getElementById('pawaDnsOverview');
  const answersEl = document.getElementById('pawaDnsAnswers');
  const mailEl = document.getElementById('pawaDnsMail');
  const fixCardsEl = document.getElementById('pawaDnsFixCards');
  const commandsEl = document.getElementById('pawaDnsCommands');
  const rawEl = document.getElementById('pawaDnsRaw');

  let lastReport = '';
  let lastCommands = '';

  const recordTypes = ['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS', 'SOA', 'CAA', 'HTTPS'];

  const statusMap = {
    0: 'NOERROR',
    1: 'FORMERR',
    2: 'SERVFAIL',
    3: 'NXDOMAIN',
    4: 'NOTIMP',
    5: 'REFUSED'
  };

  function escapeHtml(value) {
    return String(value ?? '')
      .replaceAll('&', '&amp;')
      .replaceAll('<', '&lt;')
      .replaceAll('>', '&gt;')
      .replaceAll('"', '&quot;')
      .replaceAll("'", '&#039;');
  }

  function clearError() {
    errorEl.textContent = '';
  }

  function showError(message) {
    errorEl.textContent = message;
  }

  function normalizeDomain(value) {
    let domain = String(value || '').trim().toLowerCase();

    domain = domain.replace(/^https?:\/\//, '');
    domain = domain.split('/')[0];
    domain = domain.split('?')[0];
    domain = domain.split('#')[0];
    domain = domain.replace(/\.$/, '');

    if (!domain) {
      throw new Error('Enter a domain.');
    }

    if (!/^[a-z0-9.-]+\.[a-z]{2,}$/i.test(domain)) {
      throw new Error('Invalid domain format.');
    }

    if (domain.includes('..')) {
      throw new Error('Invalid domain format.');
    }

    return domain;
  }

  function normalizeTxt(value) {
    return String(value || '')
      .replace(/^"/, '')
      .replace(/"$/, '')
      .replace(/"\s+"/g, '');
  }

  async function dnsQuery(name, type) {
    const url = 'https://cloudflare-dns.com/dns-query?name=' +
      encodeURIComponent(name) +
      '&type=' +
      encodeURIComponent(type);

    const response = await fetch(url, {
      headers: {
        'Accept': 'application/dns-json'
      }
    });

    if (!response.ok) {
      throw new Error('DNS query failed for ' + name + ' ' + type + ': HTTP ' + response.status);
    }

    return response.json();
  }

  function getAnswers(result) {
    return result && Array.isArray(result.Answer) ? result.Answer : [];
  }

  function statusText(result) {
    const code = result && typeof result.Status === 'number' ? result.Status : -1;
    return statusMap[code] || ('STATUS_' + code);
  }

  function hasAnswers(result) {
    return getAnswers(result).length > 0;
  }

  function answersFor(results, type) {
    return getAnswers(results[type] || {});
  }

  function txtRecords(result) {
    return getAnswers(result).map(function (answer) {
      return normalizeTxt(answer.data);
    });
  }

  function findSpf(txts) {
    return txts.filter(function (txt) {
      return /^v=spf1/i.test(txt);
    });
  }

  function findDmarc(txts) {
    return txts.filter(function (txt) {
      return /^v=DMARC1/i.test(txt);
    });
  }

  function parseSelectors(value) {
    return String(value || '')
      .split(',')
      .map(function (v) { return v.trim(); })
      .filter(Boolean)
      .slice(0, 10);
  }

  function buildCommands(domain, selectors) {
    const lines = [
      'dig ' + domain + ' A',
      'dig ' + domain + ' AAAA',
      'dig ' + domain + ' CNAME',
      'dig ' + domain + ' MX',
      'dig ' + domain + ' TXT',
      'dig ' + domain + ' NS',
      'dig ' + domain + ' SOA',
      'dig ' + domain + ' CAA',
      'dig ' + domain + ' HTTPS',
      'dig _dmarc.' + domain + ' TXT'
    ];

    selectors.forEach(function (selector) {
      lines.push('dig ' + selector + '._domainkey.' + domain + ' TXT');
    });

    lines.push('');
    lines.push('# Compare public resolvers');
    lines.push('dig @1.1.1.1 ' + domain + ' A');
    lines.push('dig @8.8.8.8 ' + domain + ' A');

    return lines.join('\n');
  }

  function renderOverview(results) {
    let html = '<table><thead><tr><th>Type</th><th>Status</th><th>Answers</th><th>Min TTL</th></tr></thead><tbody>';

    recordTypes.forEach(function (type) {
      const result = results[type];
      const answers = getAnswers(result);
      const ttlValues = answers.map(function (a) { return a.TTL; }).filter(function (v) { return typeof v === 'number'; });
      const minTtl = ttlValues.length ? Math.min.apply(null, ttlValues) : '-';

      html += '<tr>';
      html += '<td><code>' + escapeHtml(type) + '</code></td>';
      html += '<td>' + escapeHtml(statusText(result)) + '</td>';
      html += '<td>' + escapeHtml(answers.length) + '</td>';
      html += '<td>' + escapeHtml(minTtl) + '</td>';
      html += '</tr>';
    });

    html += '</tbody></table>';
    overviewEl.innerHTML = html;
  }

  function renderAnswers(results, dmarcResult, dkimResults) {
    let html = '';

    recordTypes.forEach(function (type) {
      const answers = answersFor(results, type);

      html += '<h4>' + escapeHtml(type) + '</h4>';

      if (!answers.length) {
        html += '<p>No answer.</p>';
        return;
      }

      html += '<table><thead><tr><th>Name</th><th>TTL</th><th>Data</th></tr></thead><tbody>';

      answers.forEach(function (answer) {
        html += '<tr>';
        html += '<td><code>' + escapeHtml(answer.name) + '</code></td>';
        html += '<td>' + escapeHtml(answer.TTL) + '</td>';
        html += '<td><code>' + escapeHtml(answer.data) + '</code></td>';
        html += '</tr>';
      });

      html += '</tbody></table>';
    });

    html += '<h4>DMARC</h4>';
    const dmarcAnswers = getAnswers(dmarcResult);

    if (!dmarcAnswers.length) {
      html += '<p>No DMARC answer.</p>';
    } else {
      html += '<table><thead><tr><th>Name</th><th>TTL</th><th>Data</th></tr></thead><tbody>';
      dmarcAnswers.forEach(function (answer) {
        html += '<tr><td><code>' + escapeHtml(answer.name) + '</code></td><td>' + escapeHtml(answer.TTL) + '</td><td><code>' + escapeHtml(answer.data) + '</code></td></tr>';
      });
      html += '</tbody></table>';
    }

    html += '<h4>DKIM selectors tested</h4>';

    if (!Object.keys(dkimResults).length) {
      html += '<p>No DKIM selector tested.</p>';
    } else {
      html += '<table><thead><tr><th>Selector</th><th>Status</th><th>Answers</th></tr></thead><tbody>';

      Object.keys(dkimResults).forEach(function (selector) {
        const result = dkimResults[selector];
        html += '<tr>';
        html += '<td><code>' + escapeHtml(selector) + '</code></td>';
        html += '<td>' + escapeHtml(statusText(result)) + '</td>';
        html += '<td>' + escapeHtml(getAnswers(result).length) + '</td>';
        html += '</tr>';
      });

      html += '</tbody></table>';
    }

    answersEl.innerHTML = html;
  }

  function buildMailSecurity(results, dmarcResult, dkimResults) {
    const txts = txtRecords(results.TXT || {});
    const spf = findSpf(txts);
    const dmarc = findDmarc(txtRecords(dmarcResult || {}));

    const dkimFound = Object.keys(dkimResults).filter(function (selector) {
      return getAnswers(dkimResults[selector]).some(function (answer) {
        return /v=DKIM1/i.test(normalizeTxt(answer.data));
      });
    });

    let html = '<table><tbody>';

    html += '<tr><th>MX</th><td>' + escapeHtml(hasAnswers(results.MX) ? 'found' : 'missing') + '</td></tr>';
    html += '<tr><th>SPF</th><td>' + escapeHtml(spf.length ? spf.join(' | ') : 'missing') + '</td></tr>';
    html += '<tr><th>DMARC</th><td>' + escapeHtml(dmarc.length ? dmarc.join(' | ') : 'missing') + '</td></tr>';
    html += '<tr><th>DKIM selectors found</th><td>' + escapeHtml(dkimFound.length ? dkimFound.join(', ') : 'none found among tested selectors') + '</td></tr>';

    html += '</tbody></table>';

    if (!dkimFound.length) {
      html += '<p>DKIM cannot be fully checked without knowing the real selector used by the email provider.</p>';
    }

    mailEl.innerHTML = html;

    return {
      spf: spf,
      dmarc: dmarc,
      dkimFound: dkimFound
    };
  }

  function addCard(cards, title, impact, next, priority) {
    cards.push({
      title: title,
      impact: impact,
      next: next,
      priority: priority
    });
  }

  function buildFixCards(domain, results, mailInfo, dmarcResult) {
    const cards = [];

    if ((results.A && results.A.Status === 3) || (results.NS && results.NS.Status === 3)) {
      addCard(
        cards,
        'Domain appears to return NXDOMAIN',
        'The domain may not exist publicly or may be misspelled.',
        'Check the domain spelling, registration status and authoritative nameservers.',
        'High'
      );
    }

    if (!hasAnswers(results.A) && !hasAnswers(results.AAAA) && !hasAnswers(results.CNAME)) {
      addCard(
        cards,
        'No web address record found',
        'The domain may not point to a web server.',
        'Add an A, AAAA or CNAME record depending on the hosting provider.',
        'High if the domain should host a website'
      );
    }

    if (hasAnswers(results.A) && !hasAnswers(results.AAAA)) {
      addCard(
        cards,
        'No IPv6 record found',
        'This is not always an error, but IPv6 clients will use IPv4 only.',
        'Add an AAAA record only if the hosting stack supports IPv6 correctly.',
        'Low to medium'
      );
    }

    if (!hasAnswers(results.MX)) {
      addCard(
        cards,
        'No MX record found',
        'The domain may not receive email properly.',
        'Add MX records from the email provider if the domain must receive mail.',
        'High if the domain uses email'
      );
    }

    if (!mailInfo.spf.length) {
      addCard(
        cards,
        'SPF record is missing',
        'The domain has weaker protection against unauthorized email sending.',
        'Add a TXT record starting with v=spf1 based on the real email providers.',
        'Medium to high'
      );
    }

    if (mailInfo.spf.length > 1) {
      addCard(
        cards,
        'Multiple SPF records found',
        'Multiple SPF records can break SPF validation.',
        'Merge all SPF mechanisms into a single TXT record.',
        'High'
      );
    }

    if (mailInfo.spf.some(function (record) { return /\+all/i.test(record); })) {
      addCard(
        cards,
        'SPF uses +all',
        '+all authorizes everything and defeats the purpose of SPF.',
        'Replace +all with a stricter mechanism such as ~all or -all after validation.',
        'High'
      );
    }

    if (!mailInfo.dmarc.length) {
      addCard(
        cards,
        'DMARC record is missing',
        'The domain has weaker protection against spoofing and poor visibility into email abuse.',
        '_dmarc.' + domain + ' TXT "v=DMARC1; p=none; rua=mailto:dmarc@' + domain + '"',
        'Medium to high if the domain sends email'
      );
    }

    if (mailInfo.dmarc.some(function (record) { return /p=none/i.test(record); })) {
      addCard(
        cards,
        'DMARC is in monitoring mode',
        'p=none collects data but does not ask receivers to quarantine or reject failing mail.',
        'Review reports, fix legitimate senders, then consider quarantine or reject progressively.',
        'Medium'
      );
    }

    if (mailInfo.dmarc.length && !mailInfo.dmarc.some(function (record) { return /rua=/i.test(record); })) {
      addCard(
        cards,
        'DMARC has no aggregate report address',
        'Without rua, you lose visibility into DMARC aggregate reports.',
        'Add rua=mailto:... if you have a mailbox or report processor ready.',
        'Low to medium'
      );
    }

    if (!hasAnswers(results.CAA)) {
      addCard(
        cards,
        'No CAA record found',
        'This is not always an error, but CAA can restrict which certificate authorities may issue certificates.',
        'Consider adding CAA records for the certificate authorities you actually use.',
        'Low to medium'
      );
    }

    if (!mailInfo.dkimFound.length) {
      addCard(
        cards,
        'No DKIM record found for tested selectors',
        'This does not prove DKIM is missing, but none of the tested selectors returned a DKIM key.',
        'Check the real selector from the email provider and test selector._domainkey.' + domain + '.',
        'Medium if the domain sends email'
      );
    }

    if (!cards.length) {
      addCard(
        cards,
        'No obvious issue detected by basic checks',
        'The main records queried returned usable signals.',
        'Still verify provider-specific requirements, mail alignment, TTL and production behavior.',
        'Informational'
      );
    }

    return cards;
  }

  function renderFixCards(cards) {
    let html = '';

    cards.forEach(function (card) {
      html += '<section>';
      html += '<h4>' + escapeHtml(card.title) + '</h4>';
      html += '<p><strong>Impact:</strong> ' + escapeHtml(card.impact) + '</p>';
      html += '<p><strong>Next step:</strong> ' + escapeHtml(card.next) + '</p>';
      html += '<p><strong>Priority:</strong> ' + escapeHtml(card.priority) + '</p>';
      html += '</section>';
    });

    fixCardsEl.innerHTML = html;
  }

  function buildReport(domain, results, mailInfo, cards, commands) {
    const lines = [
      'PawaDNS report',
      '',
      'Domain: ' + domain,
      '',
      'Records overview:'
    ];

    recordTypes.forEach(function (type) {
      const result = results[type];
      lines.push('- ' + type + ': ' + statusText(result) + ', answers=' + getAnswers(result).length);
    });

    lines.push('');
    lines.push('Mail security:');
    lines.push('- MX: ' + (hasAnswers(results.MX) ? 'found' : 'missing'));
    lines.push('- SPF: ' + (mailInfo.spf.length ? mailInfo.spf.join(' | ') : 'missing'));
    lines.push('- DMARC: ' + (mailInfo.dmarc.length ? mailInfo.dmarc.join(' | ') : 'missing'));
    lines.push('- DKIM found among tested selectors: ' + (mailInfo.dkimFound.length ? mailInfo.dkimFound.join(', ') : 'none'));

    lines.push('');
    lines.push('Fix Cards:');

    cards.forEach(function (card, index) {
      lines.push(String(index + 1) + '. ' + card.title);
      lines.push('   Impact: ' + card.impact);
      lines.push('   Next: ' + card.next);
      lines.push('   Priority: ' + card.priority);
    });

    lines.push('');
    lines.push('Commands:');
    lines.push(commands);

    return lines.join('\n');
  }

  async function analyzeDns() {
    clearError();

    try {
      const domain = normalizeDomain(domainEl.value);
      const selectors = parseSelectors(selectorsEl.value);

      summaryEl.textContent = 'Querying DNS records...';
      overviewEl.innerHTML = '';
      answersEl.innerHTML = '';
      mailEl.innerHTML = '';
      fixCardsEl.innerHTML = '';
      rawEl.textContent = '';

      const results = {};
      await Promise.all(recordTypes.map(async function (type) {
        try {
          results[type] = await dnsQuery(domain, type);
        } catch (error) {
          results[type] = { Status: -1, error: error.message, Answer: [] };
        }
      }));

      let dmarcResult;
      try {
        dmarcResult = await dnsQuery('_dmarc.' + domain, 'TXT');
      } catch (error) {
        dmarcResult = { Status: -1, error: error.message, Answer: [] };
      }

      const dkimResults = {};
      await Promise.all(selectors.map(async function (selector) {
        try {
          dkimResults[selector] = await dnsQuery(selector + '._domainkey.' + domain, 'TXT');
        } catch (error) {
          dkimResults[selector] = { Status: -1, error: error.message, Answer: [] };
        }
      }));

      renderOverview(results);
      renderAnswers(results, dmarcResult, dkimResults);

      const mailInfo = buildMailSecurity(results, dmarcResult, dkimResults);
      const cards = buildFixCards(domain, results, mailInfo, dmarcResult);
      renderFixCards(cards);

      lastCommands = buildCommands(domain, selectors);
      commandsEl.textContent = lastCommands;

      lastReport = buildReport(domain, results, mailInfo, cards, lastCommands);

      summaryEl.textContent = [
        'Domain: ' + domain,
        'A records: ' + getAnswers(results.A).length,
        'AAAA records: ' + getAnswers(results.AAAA).length,
        'MX records: ' + getAnswers(results.MX).length,
        'SPF: ' + (mailInfo.spf.length ? 'found' : 'missing'),
        'DMARC: ' + (mailInfo.dmarc.length ? 'found' : 'missing'),
        'DKIM selectors found: ' + (mailInfo.dkimFound.length ? mailInfo.dkimFound.join(', ') : 'none among tested selectors'),
        'Fix Cards: ' + cards.length
      ].join('\n');

      rawEl.textContent = JSON.stringify({
        domain: domain,
        records: results,
        dmarc: dmarcResult,
        dkim: dkimResults
      }, null, 2);
    } catch (error) {
      showError(error.message);
      summaryEl.textContent = 'DNS analysis failed.';
    }
  }

  analyzeBtn.addEventListener('click', analyzeDns);

  domainEl.addEventListener('keydown', function (event) {
    if (event.key === 'Enter') {
      analyzeDns();
    }
  });

  copyReportBtn.addEventListener('click', function () {
    navigator.clipboard.writeText(lastReport || summaryEl.textContent).then(function () {
      copyReportBtn.textContent = 'Copied';
      setTimeout(function () {
        copyReportBtn.textContent = 'Copy report';
      }, 1200);
    });
  });

  copyCommandsBtn.addEventListener('click', function () {
    navigator.clipboard.writeText(lastCommands || commandsEl.textContent).then(function () {
      copyCommandsBtn.textContent = 'Copied';
      setTimeout(function () {
        copyCommandsBtn.textContent = 'Copy dig commands';
      }, 1200);
    });
  });

  resetBtn.addEventListener('click', function () {
    domainEl.value = '';
    selectorsEl.value = 'default,google,selector1,selector2,k1';
    errorEl.textContent = '';
    summaryEl.textContent = 'Enter a domain and click “Analyze DNS”.';
    overviewEl.innerHTML = '';
    answersEl.innerHTML = '';
    mailEl.innerHTML = '';
    fixCardsEl.innerHTML = '';
    commandsEl.textContent = '';
    rawEl.textContent = '';
    lastReport = '';
    lastCommands = '';
  });
})();
</script>
]]></content:encoded>
					
					<wfw:commentRss>https://pawaops.com/pawa-dns/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>PawaSeal — Encrypted Secret Link</title>
		<link>https://pawaops.com/pawa-seal/</link>
					<comments>https://pawaops.com/pawa-seal/#respond</comments>
		
		<dc:creator><![CDATA[Admin Pawaops]]></dc:creator>
		<pubDate>Wed, 17 Jun 2026 20:12:51 +0000</pubDate>
				<category><![CDATA[Privacy & Security]]></category>
		<guid isPermaLink="false">https://pawaops.com/?p=246</guid>

					<description><![CDATA[Chiffrez un secret directement dans votre navigateur et générez un lien partageable. PawaSeal permet de chiffrer un mot de passe temporaire, un token, une variable .env, une note d’incident ou une information sensible avant de la transmettre. Le contenu est chiffré côté navigateur avec l’API Web Crypto. PawaSeal encrypts secrets directly in your browser and ... <a title="PawaSeal — Encrypted Secret Link" class="read-more" href="https://pawaops.com/pawa-seal/" aria-label="Read more about PawaSeal — Encrypted Secret Link">Read more</a>]]></description>
										<content:encoded><![CDATA[
<article class="pawa-tool pawa-seal-tool">


  <p>
    <strong>Chiffrez un secret directement dans votre navigateur et générez un lien partageable.</strong>
  </p>

  <p>
    PawaSeal permet de chiffrer un mot de passe temporaire, un token, une variable
    <code>.env</code>, une note d’incident ou une information sensible avant de la transmettre.
    Le contenu est chiffré côté navigateur avec l’API Web Crypto.
  </p>

  <p lang="en">
    PawaSeal encrypts secrets directly in your browser and generates a sealed link that can be shared.
    Useful for temporary passwords, tokens, <code>.env</code> snippets, incident notes and sensitive Ops data.
  </p>

  <hr>

  <section id="pawa-seal-app">
    <h2>Outil / Tool</h2>

    <p>
      <label for="pawaSealSecret"><strong>Secret to seal</strong></label><br>
      <textarea id="pawaSealSecret" rows="8" placeholder="Paste a password, token, .env snippet, recovery code or sensitive note here..."></textarea>
    </p>

    <p>
      <label for="pawaSealTtl"><strong>Expiration</strong></label><br>
      <select id="pawaSealTtl">
        <option value="600">10 minutes</option>
        <option value="3600" selected>1 hour</option>
        <option value="86400">24 hours</option>
        <option value="604800">7 days</option>
        <option value="0">No expiration inside payload</option>
      </select>
    </p>

    <p>
      <label>
        <input type="checkbox" id="pawaSealSplitMode" checked>
        Split mode: generate a sealed link and a separate key
      </label>
    </p>

    <p>
      <button type="button" id="pawaSealEncrypt">Seal secret</button>
      <button type="button" id="pawaSealClear">Clear</button>
    </p>

    <div id="pawaSealError" role="alert"></div>

    <h3>Sealed link</h3>
    <p>
      <textarea id="pawaSealLink" rows="4" readonly placeholder="The sealed link will appear here..."></textarea>
    </p>

    <p>
      <button type="button" id="pawaSealCopyLink">Copy sealed link</button>
    </p>

    <h3>Separate key</h3>
    <p>
      <input id="pawaSealKey" type="text" readonly placeholder="The separate key will appear here when split mode is enabled.">
    </p>

    <p>
      <button type="button" id="pawaSealCopyKey">Copy key</button>
    </p>

    <h3>Decrypt / Open sealed content</h3>

    <p>
      <label for="pawaSealPayloadInput"><strong>Sealed payload or sealed link</strong></label><br>
      <textarea id="pawaSealPayloadInput" rows="5" placeholder="Paste a PawaSeal link or payload here..."></textarea>
    </p>

    <p>
      <label for="pawaSealKeyInput"><strong>Key, if separate</strong></label><br>
      <input id="pawaSealKeyInput" type="text" placeholder="Paste the separate key here if needed">
    </p>

    <p>
      <button type="button" id="pawaSealDecrypt">Decrypt</button>
      <button type="button" id="pawaSealCopySecret">Copy decrypted secret</button>
    </p>

    <h3>Decrypted secret</h3>
    <p>
      <textarea id="pawaSealDecrypted" rows="8" readonly placeholder="The decrypted secret will appear here..."></textarea>
    </p>

    <h3>Status</h3>
    <pre id="pawaSealStatus">Ready.</pre>
  </section>

  <hr>

  <section class="pawa-article-content">
    <h2>Pourquoi PawaSeal existe</h2>

    <p>
      En administration systèmes, on finit toujours par devoir transmettre une information sensible :
      un mot de passe temporaire, une clé d’API, un token de récupération, un accès VPN, une variable
      d’environnement, une configuration de test ou une note d’incident.
    </p>

    <p>
      Le mauvais réflexe consiste à coller cette information en clair dans un email, un ticket, un chat,
      un canal Discord, un message Slack ou une documentation temporaire. C’est pratique sur le moment,
      mais dangereux à moyen terme. Le secret reste souvent indexé, synchronisé, sauvegardé, transféré
      ou oublié dans un historique.
    </p>

    <p>
      PawaSeal répond à ce problème simple : chiffrer rapidement une donnée sensible avant de la transmettre.
      L’objectif n’est pas de remplacer un gestionnaire de secrets d’entreprise, mais de fournir un outil
      pratique pour les échanges ponctuels.
    </p>

    <h2>Ce que fait l’outil</h2>

    <p>
      PawaSeal prend un texte sensible, le chiffre dans le navigateur, puis génère un lien contenant le
      contenu chiffré. La clé de déchiffrement peut être intégrée dans le lien complet ou séparée du lien
      en mode split.
    </p>

    <p>
      Le mode recommandé est le mode séparé :
    </p>

    <ol>
      <li>envoyer le lien chiffré par un canal ;</li>
      <li>envoyer la clé par un autre canal ;</li>
      <li>éviter de mettre lien et clé dans le même message.</li>
    </ol>

    <p>
      Exemple : envoyer le lien dans un ticket interne et communiquer la clé par téléphone, SMS ou message
      séparé. Cela ne rend pas l’échange parfait, mais réduit fortement le risque lié à l’interception ou à
      l’historique d’un seul canal.
    </p>

    <h2>Ce que l’outil ne fait pas</h2>

    <p>
      Cette version de PawaSeal est volontairement simple et fonctionne sans backend. Cela implique plusieurs
      limites importantes.
    </p>

    <ul>
      <li>Il n’y a pas de suppression serveur, car rien n’est stocké côté serveur par cet outil.</li>
      <li>Le mode “burn after reading” réel nécessite un backend.</li>
      <li>L’expiration est incluse dans le contenu chiffré et vérifiée au déchiffrement.</li>
      <li>Si quelqu’un possède le lien complet avec la clé, il peut déchiffrer le secret.</li>
      <li>Un secret déjà compromis ne redevient pas sûr parce qu’il a été chiffré après coup.</li>
    </ul>

    <p>
      Cette honnêteté est importante. Un outil de sécurité qui promet trop devient dangereux. PawaSeal doit être
      compris comme un outil de réduction de risque, pas comme une garantie absolue.
    </p>

    <h2>Comment fonctionne le chiffrement</h2>

    <p>
      PawaSeal utilise le chiffrement symétrique AES-GCM via l’API Web Crypto disponible dans les navigateurs
      modernes. Une clé aléatoire est générée côté navigateur. Le secret est chiffré localement, puis encodé
      dans un format transportable.
    </p>

    <p>
      En mode split, le lien contient seulement le contenu chiffré. La clé est affichée séparément. Sans cette
      clé, le contenu n’est pas exploitable.
    </p>

    <p>
      En mode lien complet, la clé est incluse dans le fragment de l’URL. C’est plus pratique, mais moins robuste
      opérationnellement, car toute personne ayant le lien complet peut lire le secret.
    </p>

    <h2>Cas d’usage concrets</h2>

    <h3>Mot de passe temporaire</h3>

    <p>
      Lorsqu’un accès temporaire doit être transmis à un prestataire, un collègue ou un client, il vaut mieux
      éviter de l’envoyer en clair. PawaSeal permet de transmettre un secret lisible seulement par la personne
      qui possède la clé.
    </p>

    <h3>Token API</h3>

    <p>
      Les tokens API sont souvent copiés trop vite dans des tickets ou des chats. Même pour un environnement
      de test, cela crée une mauvaise habitude. Un token doit être transmis avec prudence et révoqué dès qu’il
      n’est plus nécessaire.
    </p>

    <h3>Snippet .env</h3>

    <p>
      Les fichiers <code>.env</code> contiennent souvent des secrets : chaînes de connexion, mots de passe,
      clés privées, tokens JWT, secrets applicatifs. En transmettre un extrait sans protection est rarement
      une bonne idée.
    </p>

    <h3>Note d’incident</h3>

    <p>
      Pendant un incident, la pression pousse à partager vite. C’est précisément dans ces moments que les secrets
      finissent dans les mauvais endroits. PawaSeal peut servir à isoler une information sensible du reste du
      rapport d’incident.
    </p>

    <h2>Bonnes pratiques d’utilisation</h2>

    <ul>
      <li>Préférer le mode “clé séparée”.</li>
      <li>Envoyer le lien et la clé par deux canaux différents.</li>
      <li>Éviter de transmettre des secrets permanents.</li>
      <li>Révoquer ou faire expirer les secrets après usage.</li>
      <li>Ne pas coller de clé privée longue durée dans un outil temporaire.</li>
      <li>Ne pas considérer un lien complet comme privé s’il circule dans plusieurs outils.</li>
    </ul>

    <h2>Différence avec un gestionnaire de mots de passe</h2>

    <p>
      Un gestionnaire de mots de passe reste la meilleure solution pour stocker et partager durablement des
      secrets dans une équipe. Il gère les permissions, l’historique, les coffres, les révocations et parfois
      les politiques d’entreprise.
    </p>

    <p>
      PawaSeal est différent. Il sert à créer un échange ponctuel, rapide, chiffré, sans compte et sans workflow
      lourd. C’est un outil de dépannage propre, pas un coffre-fort complet.
    </p>

    <h2>Position d’administrateur systèmes</h2>

    <p>
      La bonne stratégie n’est pas de multiplier les secrets transmis à la main. La bonne stratégie est de réduire
      leur durée de vie, limiter leur portée, les transmettre proprement quand c’est nécessaire, puis les révoquer.
    </p>

    <p>
      PawaSeal aide sur une partie précise du problème : la transmission ponctuelle. Pour le reste, il faut conserver
      les fondamentaux : rotation, moindre privilège, journalisation, séparation des environnements et gestion sérieuse
      des accès.
    </p>

    <p lang="en">
      PawaSeal is a practical tool for temporary encrypted sharing. It does not replace proper secret management,
      access control, rotation policies or a real password manager.
    </p>
  </section>
</article>

<script>
(function () {
  const secretEl = document.getElementById('pawaSealSecret');
  const ttlEl = document.getElementById('pawaSealTtl');
  const splitEl = document.getElementById('pawaSealSplitMode');

  const encryptBtn = document.getElementById('pawaSealEncrypt');
  const clearBtn = document.getElementById('pawaSealClear');
  const copyLinkBtn = document.getElementById('pawaSealCopyLink');
  const copyKeyBtn = document.getElementById('pawaSealCopyKey');

  const linkEl = document.getElementById('pawaSealLink');
  const keyEl = document.getElementById('pawaSealKey');
  const errorEl = document.getElementById('pawaSealError');
  const statusEl = document.getElementById('pawaSealStatus');

  const payloadInputEl = document.getElementById('pawaSealPayloadInput');
  const keyInputEl = document.getElementById('pawaSealKeyInput');
  const decryptBtn = document.getElementById('pawaSealDecrypt');
  const decryptedEl = document.getElementById('pawaSealDecrypted');
  const copySecretBtn = document.getElementById('pawaSealCopySecret');

  function setStatus(message) {
    statusEl.textContent = message;
  }

  function showError(message) {
    errorEl.textContent = message;
  }

  function clearError() {
    errorEl.textContent = '';
  }

  function bytesToBase64Url(bytes) {
    let binary = '';
    const chunkSize = 0x8000;

    for (let i = 0; i < bytes.length; i += chunkSize) {
      const chunk = bytes.subarray(i, i + chunkSize);
      binary += String.fromCharCode.apply(null, chunk);
    }

    return btoa(binary)
      .replaceAll('+', '-')
      .replaceAll('/', '_')
      .replaceAll('=', '');
  }

  function base64UrlToBytes(base64url) {
    let base64 = String(base64url)
      .replaceAll('-', '+')
      .replaceAll('_', '/');

    while (base64.length % 4) {
      base64 += '=';
    }

    const binary = atob(base64);
    const bytes = new Uint8Array(binary.length);

    for (let i = 0; i < binary.length; i++) {
      bytes[i] = binary.charCodeAt(i);
    }

    return bytes;
  }

  function stringToBytes(value) {
    return new TextEncoder().encode(value);
  }

  function bytesToString(bytes) {
    return new TextDecoder().decode(bytes);
  }

  async function generateAesKey() {
    return crypto.subtle.generateKey(
      { name: 'AES-GCM', length: 256 },
      true,
      ['encrypt', 'decrypt']
    );
  }

  async function exportKey(key) {
    const raw = await crypto.subtle.exportKey('raw', key);
    return bytesToBase64Url(new Uint8Array(raw));
  }

  async function importKey(keyString) {
    const raw = base64UrlToBytes(keyString);

    return crypto.subtle.importKey(
      'raw',
      raw,
      { name: 'AES-GCM' },
      false,
      ['encrypt', 'decrypt']
    );
  }

  async function encryptPayload(secret, ttlSeconds) {
    const now = Date.now();
    const expiresAt = ttlSeconds > 0 ? now + ttlSeconds * 1000 : null;

    const plaintextObject = {
      v: 1,
      createdAt: now,
      expiresAt: expiresAt,
      secret: secret
    };

    const plaintext = JSON.stringify(plaintextObject);
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const key = await generateAesKey();

    const ciphertext = await crypto.subtle.encrypt(
      { name: 'AES-GCM', iv: iv },
      key,
      stringToBytes(plaintext)
    );

    const exportedKey = await exportKey(key);

    const sealedObject = {
      v: 1,
      alg: 'AES-GCM',
      iv: bytesToBase64Url(iv),
      data: bytesToBase64Url(new Uint8Array(ciphertext))
    };

    const sealedPayload = bytesToBase64Url(stringToBytes(JSON.stringify(sealedObject)));

    return {
      payload: sealedPayload,
      key: exportedKey,
      expiresAt: expiresAt
    };
  }

  async function decryptPayload(payload, keyString) {
    const sealedJson = bytesToString(base64UrlToBytes(payload));
    const sealedObject = JSON.parse(sealedJson);

    if (!sealedObject || sealedObject.alg !== 'AES-GCM' || !sealedObject.iv || !sealedObject.data) {
      throw new Error('Invalid PawaSeal payload.');
    }

    const key = await importKey(keyString);
    const iv = base64UrlToBytes(sealedObject.iv);
    const ciphertext = base64UrlToBytes(sealedObject.data);

    const decrypted = await crypto.subtle.decrypt(
      { name: 'AES-GCM', iv: iv },
      key,
      ciphertext
    );

    const plaintext = bytesToString(new Uint8Array(decrypted));
    const payloadObject = JSON.parse(plaintext);

    if (payloadObject.expiresAt && Date.now() > payloadObject.expiresAt) {
      throw new Error('This sealed secret has expired.');
    }

    return payloadObject;
  }

  function buildLink(payload, key, splitMode) {
    const baseUrl = window.location.origin + window.location.pathname;

    if (splitMode) {
      return baseUrl + '#seal=' + encodeURIComponent(payload);
    }

    return baseUrl + '#seal=' + encodeURIComponent(payload) + '&key=' + encodeURIComponent(key);
  }

  function extractFromText(value) {
    const trimmed = value.trim();

    if (!trimmed) {
      throw new Error('No sealed payload or link provided.');
    }

    if (trimmed.includes('#')) {
      const hash = trimmed.substring(trimmed.indexOf('#') + 1);
      const params = new URLSearchParams(hash);

      return {
        payload: params.get('seal') || '',
        key: params.get('key') || ''
      };
    }

    if (trimmed.startsWith('seal=')) {
      const params = new URLSearchParams(trimmed);
      return {
        payload: params.get('seal') || '',
        key: params.get('key') || ''
      };
    }

    return {
      payload: trimmed,
      key: ''
    };
  }

  function formatExpiry(expiresAt) {
    if (!expiresAt) {
      return 'No expiration embedded in payload.';
    }

    return 'Expires at: ' + new Date(expiresAt).toLocaleString();
  }

  async function sealSecret() {
    clearError();

    try {
      const secret = secretEl.value;

      if (!secret.trim()) {
        throw new Error('Paste a secret before sealing.');
      }

      if (!window.crypto || !crypto.subtle) {
        throw new Error('Web Crypto API is not available in this browser.');
      }

      const ttlSeconds = Number(ttlEl.value || 0);
      const splitMode = splitEl.checked;

      const result = await encryptPayload(secret, ttlSeconds);
      const link = buildLink(result.payload, result.key, splitMode);

      linkEl.value = link;
      keyEl.value = splitMode ? result.key : '';

      if (splitMode) {
        setStatus(
          'Secret sealed.\n' +
          'Mode: split link + separate key.\n' +
          formatExpiry(result.expiresAt) + '\n\n' +
          'Send the sealed link and the key through different channels.'
        );
      } else {
        setStatus(
          'Secret sealed.\n' +
          'Mode: full link with key included.\n' +
          formatExpiry(result.expiresAt) + '\n\n' +
          'Anyone with the full link can decrypt the secret.'
        );
      }
    } catch (error) {
      showError(error.message);
      setStatus('Error.');
    }
  }

  async function openSealedFromInputs() {
    clearError();

    try {
      const extracted = extractFromText(payloadInputEl.value);
      const payload = extracted.payload;
      const key = keyInputEl.value.trim() || extracted.key;

      if (!payload) {
        throw new Error('Missing sealed payload.');
      }

      if (!key) {
        throw new Error('Missing decryption key.');
      }

      const decrypted = await decryptPayload(payload, key);

      decryptedEl.value = decrypted.secret || '';

      setStatus(
        'Secret decrypted.\n' +
        'Created at: ' + new Date(decrypted.createdAt).toLocaleString() + '\n' +
        formatExpiry(decrypted.expiresAt)
      );
    } catch (error) {
      showError(error.message);
      decryptedEl.value = '';
      setStatus('Decryption failed.');
    }
  }

  function loadFromHash() {
    if (!window.location.hash || !window.location.hash.includes('seal=')) {
      return;
    }

    const extracted = extractFromText(window.location.href);

    payloadInputEl.value = window.location.href;

    if (extracted.key) {
      keyInputEl.value = extracted.key;
    }

    setStatus(
      'Sealed payload detected in URL.\n' +
      'Click “Decrypt” to open it. If the key is separate, paste it first.'
    );
  }

  function copyValue(element, button, defaultLabel) {
    const value = element.value || element.textContent || '';

    if (!value) {
      return;
    }

    navigator.clipboard.writeText(value).then(function () {
      button.textContent = 'Copied';
      setTimeout(function () {
        button.textContent = defaultLabel;
      }, 1200);
    });
  }

  encryptBtn.addEventListener('click', sealSecret);

  clearBtn.addEventListener('click', function () {
    secretEl.value = '';
    linkEl.value = '';
    keyEl.value = '';
    payloadInputEl.value = '';
    keyInputEl.value = '';
    decryptedEl.value = '';
    clearError();
    setStatus('Cleared.');
  });

  copyLinkBtn.addEventListener('click', function () {
    copyValue(linkEl, copyLinkBtn, 'Copy sealed link');
  });

  copyKeyBtn.addEventListener('click', function () {
    copyValue(keyEl, copyKeyBtn, 'Copy key');
  });

  decryptBtn.addEventListener('click', openSealedFromInputs);

  copySecretBtn.addEventListener('click', function () {
    copyValue(decryptedEl, copySecretBtn, 'Copy decrypted secret');
  });

  loadFromHash();
})();
</script>
]]></content:encoded>
					
					<wfw:commentRss>https://pawaops.com/pawa-seal/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>PawaHeaders — HTTP Security Headers Builder</title>
		<link>https://pawaops.com/security-headers-builder/</link>
		
		<dc:creator><![CDATA[Admin Pawaops]]></dc:creator>
		<pubDate>Wed, 17 Jun 2026 20:09:40 +0000</pubDate>
				<category><![CDATA[Basics]]></category>
		<category><![CDATA[Privacy & Security]]></category>
		<guid isPermaLink="false">https://pawaops.com/?p=243</guid>

					<description><![CDATA[Générez une base de headers HTTP de sécurité propre, lisible et prête à adapter. PawaHeaders aide à générer des headers HTTP de sécurité pour un site web, un WordPress, une API, un reverse proxy ou une interface d’administration. PawaHeaders helps generate practical HTTP security headers for websites, WordPress, APIs, reverse proxies and admin panels. Outil ... <a title="PawaHeaders — HTTP Security Headers Builder" class="read-more" href="https://pawaops.com/security-headers-builder/" aria-label="Read more about PawaHeaders — HTTP Security Headers Builder">Read more</a>]]></description>
										<content:encoded><![CDATA[
<article class="pawa-tool pawa-headers-tool">
  

  <p>
    <strong>Générez une base de headers HTTP de sécurité propre, lisible et prête à adapter.</strong>
  </p>

  <p>
    PawaHeaders aide à générer des headers HTTP de sécurité pour un site web, un WordPress, une API,
    un reverse proxy ou une interface d’administration.
  </p>

  <p lang="en">
    PawaHeaders helps generate practical HTTP security headers for websites, WordPress, APIs,
    reverse proxies and admin panels.
  </p>

  <hr>

  <section id="pawa-headers-app">
    <h2>Outil / Tool</h2>

&#8220;`
<p>
  <label for="pawaHeadersProfile"><strong>Profile</strong></label><br>
  <select id="pawaHeadersProfile">
    <option value="website">Website / Site vitrine</option>
    <option value="wordpress">WordPress</option>
    <option value="api">API</option>
    <option value="admin">Admin panel</option>
    <option value="strict">Strict baseline</option>
  </select>
</p>

<p>
  <label for="pawaHeadersTarget"><strong>Output format</strong></label><br>
  <select id="pawaHeadersTarget">
    <option value="nginx">Nginx</option>
    <option value="apache">Apache</option>
    <option value="htaccess">Apache .htaccess</option>
    <option value="cloudflare">Cloudflare / Generic</option>
    <option value="node">Node / Express</option>
    <option value="php">PHP</option>
  </select>
</p>

<fieldset>
  <legend><strong>Headers to include</strong></legend>

  <label><input type="checkbox" id="pawaHeaderHsts" checked> Strict-Transport-Security</label><br>
  <label><input type="checkbox" id="pawaHeaderCsp" checked> Content-Security-Policy</label><br>
  <label><input type="checkbox" id="pawaHeaderNosniff" checked> X-Content-Type-Options</label><br>
  <label><input type="checkbox" id="pawaHeaderFrame" checked> X-Frame-Options</label><br>
  <label><input type="checkbox" id="pawaHeaderReferrer" checked> Referrer-Policy</label><br>
  <label><input type="checkbox" id="pawaHeaderPermissions" checked> Permissions-Policy</label><br>
  <label><input type="checkbox" id="pawaHeaderCoop"> Cross-Origin-Opener-Policy</label><br>
  <label><input type="checkbox" id="pawaHeaderCorp"> Cross-Origin-Resource-Policy</label>
</fieldset>

<p>
  <button type="button" id="pawaHeadersGenerate">Generate headers</button>
  <button type="button" id="pawaHeadersCopy">Copy config</button>
  <button type="button" id="pawaHeadersCopyReport">Copy report</button>
</p>

<h3>Generated configuration</h3>
<pre><code id="pawaHeadersOutput"></code></pre>

<h3>Notes</h3>
<div id="pawaHeadersNotes"></div>
&#8220;`

  </section>

  <hr>

  <section class="pawa-article-content">
    <h2>Pourquoi les headers HTTP de sécurité sont importants</h2>

&#8220;`
<p>
  Les headers HTTP de sécurité sont une couche de durcissement côté navigateur. Ils ne corrigent pas une
  application vulnérable, ne remplacent pas les mises à jour, ne sécurisent pas une authentification faible
  et ne rendent pas un site invulnérable. Mais leur absence est souvent un signe d’hygiène technique faible.
</p>

<p>
  En pratique, ces headers indiquent au navigateur comment traiter certaines situations : chargement de scripts,
  intégration en iframe, permissions accordées aux APIs navigateur, politique de référent, protection contre
  l’interprétation incorrecte des types MIME, ou comportement cross-origin.
</p>

<h2>Les headers les plus courants</h2>

<h3>Strict-Transport-Security</h3>

<p>
  <code>Strict-Transport-Security</code>, souvent appelé HSTS, force le navigateur à utiliser HTTPS pendant
  une durée donnée. Il est utile uniquement si HTTPS est correctement configuré sur le domaine et ses sous-domaines.
</p>

<pre><code>Strict-Transport-Security: max-age=31536000; includeSubDomains</code></pre>

<p>
  Attention : activer HSTS trop tôt peut créer des problèmes si certains sous-domaines ne sont pas prêts en HTTPS.
</p>

<h3>Content-Security-Policy</h3>

<p>
  <code>Content-Security-Policy</code>, ou CSP, est l’un des headers les plus puissants. Il permet de définir
  quelles sources sont autorisées pour les scripts, styles, images, polices, frames et autres ressources.
</p>

<pre><code>Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self';</code></pre>

<p>
  C’est aussi le header le plus susceptible de casser un site s’il est déployé sans test. WordPress, les thèmes,
  les plugins, les outils analytics ou les systèmes de paiement peuvent charger des ressources externes.
</p>

<h3>X-Content-Type-Options</h3>

<p>
  <code>X-Content-Type-Options: nosniff</code> demande au navigateur de ne pas deviner le type MIME d’une ressource.
  C’est un header simple, peu risqué et généralement recommandé.
</p>

<h3>X-Frame-Options</h3>

<p>
  <code>X-Frame-Options</code> limite l’intégration du site dans une iframe. Il aide à réduire certains risques
  de clickjacking.
</p>

<pre><code>X-Frame-Options: SAMEORIGIN</code></pre>

<p>
  Pour une interface d’administration, <code>DENY</code> peut être plus pertinent.
</p>

<h3>Referrer-Policy</h3>

<p>
  <code>Referrer-Policy</code> contrôle les informations envoyées dans l’en-tête Referer lorsqu’un utilisateur
  quitte une page ou charge une ressource.
</p>

<pre><code>Referrer-Policy: strict-origin-when-cross-origin</code></pre>

<h3>Permissions-Policy</h3>

<p>
  <code>Permissions-Policy</code> permet de désactiver certaines fonctionnalités navigateur comme la caméra,
  le micro ou la géolocalisation lorsqu’elles ne sont pas nécessaires.
</p>

<pre><code>Permissions-Policy: geolocation=(), microphone=(), camera=()</code></pre>

<h2>Adapter selon le contexte</h2>

<h3>Site vitrine</h3>

<p>
  Un site vitrine peut généralement utiliser une base raisonnable : HSTS si HTTPS est propre, nosniff,
  referrer policy, permissions limitées et protection iframe.
</p>

<h3>WordPress</h3>

<p>
  WordPress nécessite plus de prudence, surtout avec CSP. Les thèmes, builders et plugins peuvent utiliser
  des scripts inline, des styles inline et des ressources externes. Une politique trop stricte peut casser
  l’éditeur, les formulaires ou certaines intégrations.
</p>

<h3>API</h3>

<p>
  Une API n’a pas les mêmes besoins qu’un site web classique. Les headers navigateur sont parfois moins
  importants que CORS, l’authentification, les limites de taux, la gestion des erreurs et la journalisation.
</p>

<h3>Admin panel</h3>

<p>
  Une interface d’administration mérite une politique plus stricte : pas d’intégration iframe inutile, peu
  de ressources externes, permissions navigateur fermées et session correctement protégée.
</p>

<h2>Déployer proprement</h2>

<p>
  Après avoir généré une configuration, il faut vérifier ce qui est réellement envoyé par le serveur public :
</p>

<pre><code>curl -I https://example.com</code></pre>

<p>
  Une erreur fréquente consiste à modifier un fichier de configuration sans que le service ne l’utilise vraiment,
  ou à définir les headers à plusieurs niveaux : application, reverse proxy, CDN. Dans ce cas, les règles peuvent
  être écrasées ou devenir incohérentes.
</p>

<h2>Position d’admin systèmes</h2>

<p>
  Les headers HTTP sont une mesure de durcissement. Ils doivent être testés, documentés et adaptés au contexte.
  Un bon réglage est celui qui améliore la sécurité sans casser les parcours utilisateur ni rendre la maintenance
  opaque.
</p>

<p lang="en">
  HTTP security headers are a hardening layer. They must be tested, documented and adapted to the real service.
</p>
&#8220;`

  </section>
</article>

<script>
(function () {
  const profileEl = document.getElementById('pawaHeadersProfile');
  const targetEl = document.getElementById('pawaHeadersTarget');
  const outputEl = document.getElementById('pawaHeadersOutput');
  const notesEl = document.getElementById('pawaHeadersNotes');

  const generateBtn = document.getElementById('pawaHeadersGenerate');
  const copyBtn = document.getElementById('pawaHeadersCopy');
  const copyReportBtn = document.getElementById('pawaHeadersCopyReport');

  const checkboxes = {
    hsts: document.getElementById('pawaHeaderHsts'),
    csp: document.getElementById('pawaHeaderCsp'),
    nosniff: document.getElementById('pawaHeaderNosniff'),
    frame: document.getElementById('pawaHeaderFrame'),
    referrer: document.getElementById('pawaHeaderReferrer'),
    permissions: document.getElementById('pawaHeaderPermissions'),
    coop: document.getElementById('pawaHeaderCoop'),
    corp: document.getElementById('pawaHeaderCorp')
  };

  let lastConfig = '';
  let lastReport = '';

  function escapeHtml(value) {
    return String(value)
      .replaceAll('&', '&amp;')
      .replaceAll('<', '&lt;')
      .replaceAll('>', '&gt;')
      .replaceAll('"', '&quot;')
      .replaceAll("'", '&#039;');
  }

  function getHeaderValues(profile) {
    const base = {
      hsts: ['Strict-Transport-Security', 'max-age=31536000; includeSubDomains'],
      nosniff: ['X-Content-Type-Options', 'nosniff'],
      frame: ['X-Frame-Options', 'SAMEORIGIN'],
      referrer: ['Referrer-Policy', 'strict-origin-when-cross-origin'],
      permissions: ['Permissions-Policy', 'geolocation=(), microphone=(), camera=()'],
      coop: ['Cross-Origin-Opener-Policy', 'same-origin'],
      corp: ['Cross-Origin-Resource-Policy', 'same-origin'],
      csp: ['Content-Security-Policy', "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self';"]
    };

    if (profile === 'wordpress') {
      base.csp = ['Content-Security-Policy', "default-src 'self' https: data: 'unsafe-inline' 'unsafe-eval'; object-src 'none'; base-uri 'self'; frame-ancestors 'self';"];
    }

    if (profile === 'api') {
      base.csp = ['Content-Security-Policy', "default-src 'none'; frame-ancestors 'none'; base-uri 'none';"];
      base.frame = ['X-Frame-Options', 'DENY'];
      base.referrer = ['Referrer-Policy', 'no-referrer'];
      base.permissions = ['Permissions-Policy', 'geolocation=(), microphone=(), camera=(), payment=(), usb=()'];
    }

    if (profile === 'admin') {
      base.csp = ['Content-Security-Policy', "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'; frame-ancestors 'none';"];
      base.frame = ['X-Frame-Options', 'DENY'];
      base.referrer = ['Referrer-Policy', 'no-referrer'];
      base.permissions = ['Permissions-Policy', 'geolocation=(), microphone=(), camera=(), payment=(), usb=()'];
      base.coop = ['Cross-Origin-Opener-Policy', 'same-origin'];
      base.corp = ['Cross-Origin-Resource-Policy', 'same-origin'];
    }

    if (profile === 'strict') {
      base.hsts = ['Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload'];
      base.csp = ['Content-Security-Policy', "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests;"];
      base.frame = ['X-Frame-Options', 'DENY'];
      base.referrer = ['Referrer-Policy', 'no-referrer'];
      base.permissions = ['Permissions-Policy', 'geolocation=(), microphone=(), camera=(), payment=(), usb=(), fullscreen=()'];
      base.coop = ['Cross-Origin-Opener-Policy', 'same-origin'];
      base.corp = ['Cross-Origin-Resource-Policy', 'same-origin'];
    }

    return base;
  }

  function selectedHeaders() {
    const values = getHeaderValues(profileEl.value);
    const selected = [];

    if (checkboxes.hsts.checked) selected.push(values.hsts);
    if (checkboxes.csp.checked) selected.push(values.csp);
    if (checkboxes.nosniff.checked) selected.push(values.nosniff);
    if (checkboxes.frame.checked) selected.push(values.frame);
    if (checkboxes.referrer.checked) selected.push(values.referrer);
    if (checkboxes.permissions.checked) selected.push(values.permissions);
    if (checkboxes.coop.checked) selected.push(values.coop);
    if (checkboxes.corp.checked) selected.push(values.corp);

    return selected;
  }

  function renderLine(target, name, value) {
    if (target === 'nginx') {
      return 'add_header ' + name + ' "' + value.replaceAll('"', '\\"') + '" always;';
    }

    if (target === 'apache' || target === 'htaccess') {
      return 'Header always set ' + name + ' "' + value.replaceAll('"', '\\"') + '"';
    }

    if (target === 'cloudflare') {
      return name + ': ' + value;
    }

    if (target === 'node') {
      return 'res.setHeader("' + name + '", "' + value.replaceAll('"', '\\"') + '");';
    }

    if (target === 'php') {
      return 'header("' + name + ': ' + value.replaceAll('"', '\\"') + '");';
    }

    return name + ': ' + value;
  }

  function generateNotes(profile, headers) {
    const notes = [];

    notes.push('Profile: ' + profile);
    notes.push('Headers generated: ' + headers.length);

    if (headers.some(function (h) { return h[0] === 'Content-Security-Policy'; })) {
      notes.push('Content-Security-Policy can break scripts, fonts, images, embeds or third-party services. Test before production.');
    }

    if (headers.some(function (h) { return h[0] === 'Strict-Transport-Security'; })) {
      notes.push('Enable HSTS only when HTTPS is stable across the domain and subdomains.');
    }

    if (profile === 'wordpress') {
      notes.push('WordPress often needs a softer CSP because themes and plugins may load inline scripts or third-party resources.');
    }

    if (profile === 'api') {
      notes.push('For APIs, CORS, authentication, rate limiting and error handling are usually more important than browser-only headers.');
    }

    if (profile === 'strict') {
      notes.push('Strict mode is intentionally restrictive. Use it only after validation.');
    }

    return notes;
  }

  function generate() {
    const target = targetEl.value;
    const profile = profileEl.value;
    const headers = selectedHeaders();

    let lines = [];

    if ((target === 'apache' || target === 'htaccess') && headers.length > 0) {
      lines.push('<IfModule mod_headers.c>');
      headers.forEach(function (header) {
        lines.push('  ' + renderLine(target, header[0], header[1]));
      });
      lines.push('</IfModule>');
    } else {
      headers.forEach(function (header) {
        lines.push(renderLine(target, header[0], header[1]));
      });
    }

    lastConfig = lines.join('\n');

    const notes = generateNotes(profile, headers);

    lastReport = [
      'PawaHeaders report',
      '',
      'Profile: ' + profile,
      'Target: ' + target,
      '',
      'Configuration:',
      lastConfig,
      '',
      'Notes:',
      '- ' + notes.join('\n- ')
    ].join('\n');

    outputEl.textContent = lastConfig || 'No header selected.';

    let notesHtml = '<ul>';
    notes.forEach(function (note) {
      notesHtml += '<li>' + escapeHtml(note) + '</li>';
    });
    notesHtml += '</ul>';

    notesEl.innerHTML = notesHtml;
  }

  generateBtn.addEventListener('click', generate);

  copyBtn.addEventListener('click', function () {
    navigator.clipboard.writeText(lastConfig || outputEl.textContent).then(function () {
      copyBtn.textContent = 'Copied';
      setTimeout(function () { copyBtn.textContent = 'Copy config'; }, 1200);
    });
  });

  copyReportBtn.addEventListener('click', function () {
    navigator.clipboard.writeText(lastReport || outputEl.textContent).then(function () {
      copyReportBtn.textContent = 'Copied';
      setTimeout(function () { copyReportBtn.textContent = 'Copy report'; }, 1200);
    });
  });

  profileEl.addEventListener('change', generate);
  targetEl.addEventListener('change', generate);

  Object.keys(checkboxes).forEach(function (key) {
    checkboxes[key].addEventListener('change', generate);
  });

  generate();
})();
</script>

]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>PawaCron — Cron Expression Decoder</title>
		<link>https://pawaops.com/cron-decoder/</link>
		
		<dc:creator><![CDATA[Admin Pawaops]]></dc:creator>
		<pubDate>Wed, 17 Jun 2026 19:59:39 +0000</pubDate>
				<category><![CDATA[Basics]]></category>
		<category><![CDATA[Software]]></category>
		<guid isPermaLink="false">https://pawaops.com/?p=241</guid>

					<description><![CDATA[Comprenez une expression cron avant de la mettre en production. PawaCron décode une expression cron standard, affiche ses champs, calcule les prochaines exécutions et signale les planifications potentiellement risquées. PawaCron decodes standard cron expressions, displays their fields, previews upcoming runs and highlights potentially risky schedules. Outil / Tool &#8220;` Expression cron Decode cron Copy report ... <a title="PawaCron — Cron Expression Decoder" class="read-more" href="https://pawaops.com/cron-decoder/" aria-label="Read more about PawaCron — Cron Expression Decoder">Read more</a>]]></description>
										<content:encoded><![CDATA[
<article class="pawa-tool pawa-cron-tool">
  

  <p>
    <strong>Comprenez une expression cron avant de la mettre en production.</strong>
  </p>

  <p>
    PawaCron décode une expression cron standard, affiche ses champs, calcule les prochaines exécutions
    et signale les planifications potentiellement risquées.
  </p>

  <p lang="en">
    PawaCron decodes standard cron expressions, displays their fields, previews upcoming runs
    and highlights potentially risky schedules.
  </p>

  <hr>

  <section id="pawa-cron-app">
    <h2>Outil / Tool</h2>

&#8220;`
<p>
  <label for="pawaCronInput"><strong>Expression cron</strong></label><br>
  <input id="pawaCronInput" type="text" value="*/15 9-18 * * 1-5" placeholder="*/15 9-18 * * 1-5">
</p>

<p>
  <button type="button" id="pawaCronDecode">Decode cron</button>
  <button type="button" id="pawaCronCopy">Copy report</button>
  <button type="button" id="pawaCronReset">Reset</button>
</p>

<p>
  Exemples :
  <button type="button" class="pawaCronExample" data-cron="*/5 * * * *">Every 5 min</button>
  <button type="button" class="pawaCronExample" data-cron="0 3 * * 1">Monday 03:00</button>
  <button type="button" class="pawaCronExample" data-cron="30 2 * * *">Daily 02:30</button>
  <button type="button" class="pawaCronExample" data-cron="0 4 * * 0">Sunday 04:00</button>
</p>

<div id="pawaCronError" role="alert"></div>

<h3>Result</h3>
<pre id="pawaCronSummary">Click “Decode cron” to analyze the expression.</pre>

<h3>Parsed fields</h3>
<div id="pawaCronFields"></div>

<h3>Next runs</h3>
<div id="pawaCronRuns"></div>
&#8220;`

  </section>

  <hr>

  <section class="pawa-article-content">
    <h2>Pourquoi vérifier une expression cron ?</h2>

&#8220;`
<p>
  Cron est un outil simple, ancien et très fiable. C’est aussi pour cette raison qu’il est encore partout :
  sauvegardes, rotations de logs, synchronisations, scripts de maintenance, exports, traitements applicatifs
  ou tâches de supervision.
</p>

<p>
  Mais une expression cron mal comprise peut déclencher une tâche trop souvent, trop rarement, au mauvais
  moment ou jamais. En exploitation, ce genre d’erreur est rarement spectaculaire au début. Elle se voit
  souvent plus tard : logs qui grossissent, scripts qui se chevauchent, sauvegardes manquantes, appels API
  excessifs, charge serveur inutile ou traitements qui tournent pendant les heures sensibles.
</p>

<h2>Comment lire une expression cron standard</h2>

<p>
  Une expression cron standard contient cinq champs :
</p>

<pre><code>minute hour day-of-month month day-of-week</code></pre>

<p>
  Exemple :
</p>

<pre><code>*/15 9-18 * * 1-5</code></pre>

<p>
  Cette expression signifie : toutes les 15 minutes, entre 09:00 et 18:59, du lundi au vendredi.
</p>

<h2>Exemples courants</h2>

<h3>Toutes les cinq minutes</h3>

<pre><code>*/5 * * * *</code></pre>

<p>
  Cette planification peut être utile pour un job léger de monitoring. Elle devient risquée pour un script
  lourd, un export, une sauvegarde ou un traitement qui peut durer plus de quelques minutes.
</p>

<h3>Tous les jours à 02:30</h3>

<pre><code>30 2 * * *</code></pre>

<p>
  Ce format est classique pour des tâches nocturnes. Il faut tout de même vérifier que plusieurs jobs lourds
  ne démarrent pas tous à la même heure.
</p>

<h3>Chaque lundi à 03:00</h3>

<pre><code>0 3 * * 1</code></pre>

<p>
  Format fréquent pour des rapports hebdomadaires, nettoyages ou traitements planifiés.
</p>

<h2>Le piège du chevauchement</h2>

<p>
  Une expression cron peut être parfaitement valide et rester dangereuse. Le cas le plus classique est une
  tâche qui s’exécute plus souvent qu’elle ne se termine.
</p>

<p>
  Exemple : un script lancé toutes les minutes mais qui met parfois trois minutes à finir. Sans verrou, plusieurs
  instances peuvent tourner en parallèle.
</p>

<p>
  Une protection simple consiste à utiliser <code>flock</code> :
</p>

<pre><code>* * * * * flock -n /tmp/my-job.lock /usr/local/bin/my-job.sh</code></pre>

<p>
  Cela empêche une nouvelle exécution de démarrer si la précédente est encore active.
</p>

<h2>Bonnes pratiques avant mise en production</h2>

<ul>
  <li>Tester la commande manuellement avant de la planifier.</li>
  <li>Utiliser des chemins absolus.</li>
  <li>Vérifier l’utilisateur qui exécute la tâche.</li>
  <li>Rediriger correctement la sortie standard et les erreurs.</li>
  <li>Ajouter des logs exploitables.</li>
  <li>Prévoir un verrou si la tâche ne doit pas se chevaucher.</li>
  <li>Documenter la fréquence et l’objectif du job.</li>
</ul>

<h2>Position d’admin systèmes</h2>

<p>
  Le bon réflexe n’est pas seulement de demander si une expression cron est valide. La vraie question est :
  cette tâche peut-elle tourner à cette fréquence, avec cette durée, sur cette machine, sans effet secondaire ?
</p>

<p>
  PawaCron aide à lever l’ambiguïté sur la syntaxe et la fréquence. Le jugement opérationnel reste essentiel.
</p>

<p lang="en">
  PawaCron helps remove ambiguity around cron syntax and frequency. Operational judgment still matters.
</p>
&#8220;`

  </section>
</article>

<script>
(function () {
  const input = document.getElementById('pawaCronInput');
  const decodeBtn = document.getElementById('pawaCronDecode');
  const copyBtn = document.getElementById('pawaCronCopy');
  const resetBtn = document.getElementById('pawaCronReset');
  const summaryEl = document.getElementById('pawaCronSummary');
  const runsEl = document.getElementById('pawaCronRuns');
  const fieldsEl = document.getElementById('pawaCronFields');
  const errorEl = document.getElementById('pawaCronError');

  const monthNames = { JAN:1,FEB:2,MAR:3,APR:4,MAY:5,JUN:6,JUL:7,AUG:8,SEP:9,OCT:10,NOV:11,DEC:12 };
  const dayNames = { SUN:0,MON:1,TUE:2,WED:3,THU:4,FRI:5,SAT:6 };
  let lastReport = '';

  function escapeHtml(value) {
    return String(value)
      .replaceAll('&', '&amp;')
      .replaceAll('<', '&lt;')
      .replaceAll('>', '&gt;')
      .replaceAll('"', '&quot;')
      .replaceAll("'", '&#039;');
  }

  function showError(message) {
    errorEl.textContent = message;
  }

  function clearError() {
    errorEl.textContent = '';
  }

  function normalizeToken(token, names, isDow) {
    const upper = String(token).trim().toUpperCase();

    if (names && Object.prototype.hasOwnProperty.call(names, upper)) {
      return names[upper];
    }

    if (!/^\d+$/.test(upper)) {
      throw new Error('Invalid value: ' + token);
    }

    let value = Number(upper);
    if (isDow && value === 7) value = 0;
    return value;
  }

  function parseField(raw, min, max, label, names, isDow) {
    const set = new Set();
    const original = raw.trim();
    const any = original === '*' || original === '?';
    const parts = original.split(',');

    parts.forEach(function (part) {
      part = part.trim();
      if (!part) throw new Error('Invalid list in field: ' + label);

      let rangePart = part;
      let step = 1;

      if (part.includes('/')) {
        const split = part.split('/');
        if (split.length !== 2) throw new Error('Invalid step in: ' + part);

        rangePart = split[0];
        step = Number(split[1]);

        if (!Number.isInteger(step) || step <= 0) {
          throw new Error('Invalid step in: ' + part);
        }
      }

      let start;
      let end;

      if (rangePart === '*' || rangePart === '?') {
        start = min;
        end = max;
      } else if (rangePart.includes('-')) {
        const split = rangePart.split('-');
        if (split.length !== 2) throw new Error('Invalid range in: ' + part);

        start = normalizeToken(split[0], names, isDow);
        end = normalizeToken(split[1], names, isDow);
      } else {
        start = normalizeToken(rangePart, names, isDow);
        end = start;
      }

      if (start < min || start > max || end < min || end > max) {
        throw new Error(label + ' out of bounds: ' + part);
      }

      if (start <= end) {
        for (let i = start; i <= end; i += step) set.add(i);
      } else if (isDow) {
        for (let i = start; i <= max; i += step) set.add(i);
        for (let i = min; i <= end; i += step) set.add(i);
      } else {
        throw new Error('Invalid reversed range in: ' + part);
      }
    });

    return {
      original: original,
      values: Array.from(set).sort(function (a, b) { return a - b; }),
      any: any
    };
  }

  function parseCron(expr) {
    const parts = expr.trim().replace(/\s+/g, ' ').split(' ');

    if (parts.length !== 5) {
      throw new Error('A standard cron expression must contain 5 fields.');
    }

    return {
      original: expr.trim(),
      minute: parseField(parts[0], 0, 59, 'minute'),
      hour: parseField(parts[1], 0, 23, 'hour'),
      dom: parseField(parts[2], 1, 31, 'day of month'),
      month: parseField(parts[3], 1, 12, 'month', monthNames),
      dow: parseField(parts[4], 0, 6, 'day of week', dayNames, true)
    };
  }

  function matches(date, cron) {
    const minute = date.getMinutes();
    const hour = date.getHours();
    const dom = date.getDate();
    const month = date.getMonth() + 1;
    const dow = date.getDay();

    if (!cron.minute.values.includes(minute)) return false;
    if (!cron.hour.values.includes(hour)) return false;
    if (!cron.month.values.includes(month)) return false;

    const domMatch = cron.dom.values.includes(dom);
    const dowMatch = cron.dow.values.includes(dow);

    if (cron.dom.any &#038;&#038; cron.dow.any) return true;
    if (cron.dom.any) return dowMatch;
    if (cron.dow.any) return domMatch;

    return domMatch || dowMatch;
  }

  function nextRuns(cron, count) {
    const result = [];
    const cursor = new Date();

    cursor.setSeconds(0, 0);
    cursor.setMinutes(cursor.getMinutes() + 1);

    const maxChecks = 60 * 24 * 366 * 2;

    for (let i = 0; i < maxChecks &#038;&#038; result.length < count; i++) {
      if (matches(cursor, cron)) result.push(new Date(cursor));
      cursor.setMinutes(cursor.getMinutes() + 1);
    }

    return result;
  }

  function fieldText(field) {
    if (field.any) return 'any';
    if (field.values.length > 20) return field.values.length + ' values';
    return field.values.join(', ');
  }

  function detectWarnings(cron, runs) {
    const warnings = [];

    if (cron.minute.original === '*' && cron.hour.original === '*') {
      warnings.push('Runs every minute. Use only for very lightweight and controlled jobs.');
    }

    if (/^\*\/[1-5]$/.test(cron.minute.original) && cron.hour.original === '*') {
      warnings.push('High frequency detected. Check script duration and consider a lock mechanism.');
    }

    if (!cron.dom.any && !cron.dow.any) {
      warnings.push('Both day-of-month and day-of-week are restricted. Many cron implementations apply OR logic here.');
    }

    if (runs.length === 0) {
      warnings.push('No run found in the next 2 years. Check the date logic.');
    }

    return warnings;
  }

  function renderFields(cron) {
    const rows = [
      ['Minute', cron.minute.original, fieldText(cron.minute)],
      ['Hour', cron.hour.original, fieldText(cron.hour)],
      ['Day of month', cron.dom.original, fieldText(cron.dom)],
      ['Month', cron.month.original, fieldText(cron.month)],
      ['Day of week', cron.dow.original, fieldText(cron.dow)]
    ];

    let html = '<table><thead><tr><th>Field</th><th>Raw</th><th>Parsed</th></tr></thead><tbody>';

    rows.forEach(function (row) {
      html += '<tr><td>' + escapeHtml(row[0]) + '</td><td><code>' + escapeHtml(row[1]) + '</code></td><td>' + escapeHtml(row[2]) + '</td></tr>';
    });

    html += '</tbody></table>';
    fieldsEl.innerHTML = html;
  }

  function renderRuns(runs) {
    if (!runs.length) {
      runsEl.innerHTML = '<p>No upcoming run found.</p>';
      return;
    }

    let html = '<ol>';
    runs.forEach(function (date) {
      html += '<li>' + escapeHtml(date.toLocaleString()) + '</li>';
    });
    html += '</ol>';

    runsEl.innerHTML = html;
  }

  function decode() {
    clearError();

    try {
      const cron = parseCron(input.value);
      const runs = nextRuns(cron, 10);
      const warnings = detectWarnings(cron, runs);

      const report = [
        'Cron expression: ' + cron.original,
        '',
        'Fields:',
        '- Minute: ' + cron.minute.original + ' → ' + fieldText(cron.minute),
        '- Hour: ' + cron.hour.original + ' → ' + fieldText(cron.hour),
        '- Day of month: ' + cron.dom.original + ' → ' + fieldText(cron.dom),
        '- Month: ' + cron.month.original + ' → ' + fieldText(cron.month),
        '- Day of week: ' + cron.dow.original + ' → ' + fieldText(cron.dow),
        '',
        'Next runs:',
        runs.map(function (date, index) {
          return String(index + 1) + '. ' + date.toLocaleString();
        }).join('\n'),
        '',
        warnings.length ? 'Warnings:\n- ' + warnings.join('\n- ') : 'Warnings: none'
      ].join('\n');

      lastReport = report;
      summaryEl.textContent = report;
      renderFields(cron);
      renderRuns(runs);
    } catch (error) {
      showError(error.message);
      summaryEl.textContent = 'Invalid cron expression.';
      fieldsEl.innerHTML = '';
      runsEl.innerHTML = '';
      lastReport = '';
    }
  }

  decodeBtn.addEventListener('click', decode);

  input.addEventListener('keydown', function (event) {
    if (event.key === 'Enter') decode();
  });

  copyBtn.addEventListener('click', function () {
    if (!lastReport) decode();

    navigator.clipboard.writeText(lastReport || summaryEl.textContent).then(function () {
      copyBtn.textContent = 'Copied';
      setTimeout(function () {
        copyBtn.textContent = 'Copy report';
      }, 1200);
    });
  });

  resetBtn.addEventListener('click', function () {
    input.value = '*/15 9-18 * * 1-5';
    decode();
  });

  document.querySelectorAll('.pawaCronExample').forEach(function (button) {
    button.addEventListener('click', function () {
      input.value = button.getAttribute('data-cron');
      decode();
    });
  });

  decode();
})();
</script>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>PawaIP — What Is My IP</title>
		<link>https://pawaops.com/pawa-ip/</link>
		
		<dc:creator><![CDATA[Admin Pawaops]]></dc:creator>
		<pubDate>Wed, 10 Jun 2026 20:47:54 +0000</pubDate>
				<category><![CDATA[Basics]]></category>
		<category><![CDATA[Internet & Network]]></category>
		<guid isPermaLink="false">https://pawaops.com/?p=251</guid>

					<description><![CDATA[Your public IP address, instantly. PawaIP affiche votre adresse IP publique immédiatement, puis tente d’ajouter les détails utiles : IPv4/IPv6, géolocalisation approximative, ASN, organisation, timezone et commandes sysadmin. Your public IP Direct HTML fallback: Detected IP Copy IP Reload Copy report Open raw IP Detecting your IP&#8230; Network details Waiting for IP detection&#8230; Useful commands ... <a title="PawaIP — What Is My IP" class="read-more" href="https://pawaops.com/pawa-ip/" aria-label="Read more about PawaIP — What Is My IP">Read more</a>]]></description>
										<content:encoded><![CDATA[
<article class="pawa-tool pawa-ip-tool">
 

  <p>
    <strong>Your public IP address, instantly.</strong>
  </p>

  <p>
    PawaIP affiche votre adresse IP publique immédiatement, puis tente d’ajouter les détails utiles :
    IPv4/IPv6, géolocalisation approximative, ASN, organisation, timezone et commandes sysadmin.
  </p>

  <hr>

  <section id="pawa-ip-app">
    <h2>Your public IP</h2>

    <p>
      <strong>Direct HTML fallback:</strong>
    </p>

    <iframe
      src="https://api.ipify.org"
      title="Your public IP address"
      style="width:100%; min-height:60px; border:1px solid currentColor;"
      loading="eager">
    </iframe>

    <p>
      <label for="pawaIpMain"><strong>Detected IP</strong></label><br>
      <input id="pawaIpMain" type="text" readonly value="Detecting..." style="font-size:1.4em; width:100%;">
    </p>

    <p>
      <button type="button" id="pawaIpCopyIp">Copy IP</button>
      <button type="button" id="pawaIpReload">Reload</button>
      <button type="button" id="pawaIpCopyReport">Copy report</button>
      <a href="https://api.ipify.org" target="_blank" rel="noopener">Open raw IP</a>
    </p>

    <p id="pawaIpStatus">Detecting your IP&#8230;</p>

    <hr>

    <h3>Network details</h3>
    <div id="pawaIpDetails">Waiting for IP detection&#8230;</div>

    <h3>Useful commands</h3>
    <pre><code id="pawaIpCommands"></code></pre>

    <h3>Raw data</h3>
    <details>
      <summary>Show raw JSON</summary>
      <pre><code id="pawaIpRaw"></code></pre>
    </details>
  </section>

  <hr>

  <section class="pawa-article-content">
    <h2>About PawaIP</h2>

    <p>
      Une adresse IP publique est souvent le premier indice dans un diagnostic réseau. Elle aide à comprendre
      le point de sortie d’une connexion, le fournisseur, l’ASN, la localisation approximative et le contexte réseau.
    </p>

    <p>
      Pour un administrateur systèmes, ces informations sont utiles pour vérifier une règle firewall, diagnostiquer
      un accès bloqué, contrôler une allowlist, analyser des logs ou documenter un ticket réseau.
    </p>

    <p>
      La géolocalisation IP reste approximative. Elle peut pointer vers un opérateur, un VPN, un datacenter,
      une passerelle mobile ou un point de sortie réseau. Elle doit être utilisée comme indice, pas comme preuve exacte.
    </p>
  </section>
</article>

<script>
(function () {
  function ready(fn) {
    if (document.readyState !== 'loading') {
      fn();
    } else {
      document.addEventListener('DOMContentLoaded', fn);
    }
  }

  ready(function () {
    var ipInput = document.getElementById('pawaIpMain');
    var statusEl = document.getElementById('pawaIpStatus');
    var detailsEl = document.getElementById('pawaIpDetails');
    var commandsEl = document.getElementById('pawaIpCommands');
    var rawEl = document.getElementById('pawaIpRaw');

    var copyIpBtn = document.getElementById('pawaIpCopyIp');
    var reloadBtn = document.getElementById('pawaIpReload');
    var copyReportBtn = document.getElementById('pawaIpCopyReport');

    if (!ipInput || !statusEl) {
      return;
    }

    var currentIp = '';
    var currentReport = '';

    function escapeHtml(value) {
      return String(value == null ? '' : value)
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
    }

    function isIp(value) {
      value = String(value || '').trim();

      var ipv4 = /^(\d{1,3}\.){3}\d{1,3}$/.test(value);
      var ipv6 = /^[0-9a-fA-F:]{3,}$/.test(value) && value.indexOf(':') !== -1;

      return ipv4 || ipv6;
    }

    function ipVersion(ip) {
      if (String(ip).indexOf(':') !== -1) return 'IPv6';
      if (String(ip).indexOf('.') !== -1) return 'IPv4';
      return 'Unknown';
    }

    function jsonp(url, callbackParam, timeoutMs) {
      timeoutMs = timeoutMs || 3000;

      return new Promise(function (resolve, reject) {
        var callbackName = 'pawaIpJsonp_' + Date.now() + '_' + Math.floor(Math.random() * 100000);
        var script = document.createElement('script');
        var timeout;

        window[callbackName] = function (data) {
          cleanup();
          resolve(data);
        };

        function cleanup() {
          if (timeout) clearTimeout(timeout);
          if (script && script.parentNode) script.parentNode.removeChild(script);

          try {
            delete window[callbackName];
          } catch (e) {
            window[callbackName] = undefined;
          }
        }

        timeout = setTimeout(function () {
          cleanup();
          reject(new Error('JSONP timeout'));
        }, timeoutMs);

        script.onerror = function () {
          cleanup();
          reject(new Error('JSONP script error'));
        };

        script.src = url + (url.indexOf('?') === -1 ? '?' : '&') +
          encodeURIComponent(callbackParam) + '=' + encodeURIComponent(callbackName);

        document.head.appendChild(script);
      });
    }

    function fetchJsonWithTimeout(url, timeoutMs) {
      timeoutMs = timeoutMs || 3500;

      return new Promise(function (resolve, reject) {
        var finished = false;

        var timer = setTimeout(function () {
          if (finished) return;
          finished = true;
          reject(new Error('Timeout'));
        }, timeoutMs);

        fetch(url, {
          cache: 'no-store',
          headers: {
            'Accept': 'application/json'
          }
        })
          .then(function (response) {
            if (!response.ok) throw new Error('HTTP ' + response.status);
            return response.json();
          })
          .then(function (json) {
            if (finished) return;
            finished = true;
            clearTimeout(timer);
            resolve(json);
          })
          .catch(function (error) {
            if (finished) return;
            finished = true;
            clearTimeout(timer);
            reject(error);
          });
      });
    }

    function buildCommands(ip) {
      var q = String(ip).indexOf(':') !== -1 ? "'" + ip + "'" : ip;

      return [
        '# Get your public IP',
        'curl https://api.ipify.org',
        '',
        '# Reverse DNS',
        'dig -x ' + q,
        '',
        '# WHOIS / ASN',
        'whois ' + q,
        '',
        '# Reachability',
        'ping ' + q,
        '',
        '# Route path',
        'traceroute ' + q,
        '',
        '# TCP port check',
        'nc -vz ' + q + ' 443',
        '',
        '# Windows PowerShell',
        'Test-NetConnection ' + q + ' -Port 443'
      ].join('\n');
    }

    function renderBasic(ip, provider) {
      currentIp = ip;
      ipInput.value = ip;
      statusEl.textContent = 'IP detected via ' + provider + '. Loading details...';
      commandsEl.textContent = buildCommands(ip);

      currentReport = [
        'PawaIP report',
        '',
        'IP: ' + ip,
        'Version: ' + ipVersion(ip),
        'Provider: ' + provider,
        '',
        'Commands:',
        buildCommands(ip)
      ].join('\n');

      detailsEl.innerHTML =
        '<table><tbody>' +
        '<tr><th>IP</th><td>' + escapeHtml(ip) + '</td></tr>' +
        '<tr><th>Version</th><td>' + escapeHtml(ipVersion(ip)) + '</td></tr>' +
        '<tr><th>Status</th><td>Details loading...</td></tr>' +
        '</tbody></table>';
    }

    function renderDetails(data) {
      var ip = data.ip || currentIp;
      var version = ipVersion(ip);
      var provider = data._provider || 'detail provider';

      var city = data.city || '';
      var region = data.region || '';
      var country = data.country_name || data.country || '';
      var countryCode = data.country_code || '';
      var latitude = data.latitude || '';
      var longitude = data.longitude || '';
      var timezone = data.timezone || '';
      var asn = data.asn || '';
      var org = data.org || '';
      var network = data.network || '';

      detailsEl.innerHTML =
        '<table><tbody>' +
        '<tr><th>IP</th><td>' + escapeHtml(ip) + '</td></tr>' +
        '<tr><th>Version</th><td>' + escapeHtml(version) + '</td></tr>' +
        '<tr><th>Provider</th><td>' + escapeHtml(provider) + '</td></tr>' +
        '<tr><th>City</th><td>' + escapeHtml(city || '-') + '</td></tr>' +
        '<tr><th>Region</th><td>' + escapeHtml(region || '-') + '</td></tr>' +
        '<tr><th>Country</th><td>' + escapeHtml(country || '-') + '</td></tr>' +
        '<tr><th>Country code</th><td>' + escapeHtml(countryCode || '-') + '</td></tr>' +
        '<tr><th>Latitude</th><td>' + escapeHtml(latitude || '-') + '</td></tr>' +
        '<tr><th>Longitude</th><td>' + escapeHtml(longitude || '-') + '</td></tr>' +
        '<tr><th>Timezone</th><td>' + escapeHtml(timezone || '-') + '</td></tr>' +
        '<tr><th>ASN</th><td>' + escapeHtml(asn || '-') + '</td></tr>' +
        '<tr><th>Organization</th><td>' + escapeHtml(org || '-') + '</td></tr>' +
        '<tr><th>Network / ISP</th><td>' + escapeHtml(network || '-') + '</td></tr>' +
        '</tbody></table>';

      rawEl.textContent = JSON.stringify(data._raw || data, null, 2);

      currentReport = [
        'PawaIP report',
        '',
        'IP: ' + ip,
        'Version: ' + version,
        'Location: ' + [city, region, country].filter(Boolean).join(', '),
        'ASN: ' + (asn || '-'),
        'Organization: ' + (org || '-'),
        'Network: ' + (network || '-'),
        'Timezone: ' + (timezone || '-'),
        'Provider: ' + provider,
        '',
        'Commands:',
        buildCommands(ip),
        '',
        'Note: IP geolocation is approximate.'
      ].join('\n');

      statusEl.textContent = 'Done.';
    }

    function normalizeIpwho(data, ip) {
      return {
        ip: data.ip || ip,
        city: data.city,
        region: data.region,
        country_name: data.country,
        country_code: data.country_code,
        latitude: data.latitude,
        longitude: data.longitude,
        timezone: data.timezone && data.timezone.id ? data.timezone.id : '',
        asn: data.connection && data.connection.asn ? 'AS' + data.connection.asn : '',
        org: data.connection && data.connection.org ? data.connection.org : '',
        network: data.connection && data.connection.isp ? data.connection.isp : '',
        _provider: 'ipwho.is',
        _raw: data
      };
    }

    function loadDetails(ip) {
      fetchJsonWithTimeout('https://ipapi.co/' + encodeURIComponent(ip) + '/json/', 3500)
        .then(function (data) {
          if (data && !data.error) {
            data._provider = 'ipapi.co';
            renderDetails(data);
            return;
          }

          throw new Error('ipapi unavailable');
        })
        .catch(function () {
          return fetchJsonWithTimeout('https://ipwho.is/' + encodeURIComponent(ip), 3500)
            .then(function (data) {
              if (data && data.success !== false) {
                renderDetails(normalizeIpwho(data, ip));
                return;
              }

              throw new Error('ipwho unavailable');
            })
            .catch(function () {
              statusEl.textContent = 'IP detected. Details unavailable, probably blocked by browser, CSP, network or API limits.';
              detailsEl.innerHTML =
                '<table><tbody>' +
                '<tr><th>IP</th><td>' + escapeHtml(ip) + '</td></tr>' +
                '<tr><th>Version</th><td>' + escapeHtml(ipVersion(ip)) + '</td></tr>' +
                '<tr><th>Details</th><td>Unavailable</td></tr>' +
                '</tbody></table>';
            });
        });
    }

    function detectIp() {
      ipInput.value = 'Detecting...';
      statusEl.textContent = 'Detecting your public IP...';
      detailsEl.textContent = 'Waiting...';
      commandsEl.textContent = '';
      rawEl.textContent = '';
      currentIp = '';
      currentReport = '';

      jsonp('https://api.ipify.org?format=jsonp', 'callback', 3000)
        .then(function (data) {
          if (!data || !isIp(data.ip)) throw new Error('Invalid ipify response');

          renderBasic(data.ip, 'ipify JSONP');
          loadDetails(data.ip);
        })
        .catch(function () {
          jsonp('https://api64.ipify.org?format=jsonp', 'callback', 3000)
            .then(function (data) {
              if (!data || !isIp(data.ip)) throw new Error('Invalid ipify response');

              renderBasic(data.ip, 'ipify IPv6/IPv4 JSONP');
              loadDetails(data.ip);
            })
            .catch(function () {
              ipInput.value = 'Unavailable';
              statusEl.textContent =
                'JavaScript IP detection failed. The direct HTML fallback above may still show your IP.';
              detailsEl.textContent =
                'If the fallback iframe also stays empty, your browser, network, CSP, WordPress security plugin, or ad blocker is blocking external IP services.';
            });
        });
    }

    function copyText(value, button, label) {
      if (!value) return;

      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(value).then(function () {
          button.textContent = 'Copied';
          setTimeout(function () { button.textContent = label; }, 1200);
        });
      } else {
        var temp = document.createElement('textarea');
        temp.value = value;
        document.body.appendChild(temp);
        temp.select();
        document.execCommand('copy');
        document.body.removeChild(temp);

        button.textContent = 'Copied';
        setTimeout(function () { button.textContent = label; }, 1200);
      }
    }

    copyIpBtn.addEventListener('click', function () {
      copyText(currentIp || ipInput.value, copyIpBtn, 'Copy IP');
    });

    reloadBtn.addEventListener('click', detectIp);

    copyReportBtn.addEventListener('click', function () {
      copyText(currentReport || ipInput.value, copyReportBtn, 'Copy report');
    });

    detectIp();
  });
})();
</script>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Understanding system scalability without performance tuning</title>
		<link>https://pawaops.com/understanding-system-scalability-without-performance-tuning/</link>
					<comments>https://pawaops.com/understanding-system-scalability-without-performance-tuning/#respond</comments>
		
		<dc:creator><![CDATA[Admin Pawaops]]></dc:creator>
		<pubDate>Wed, 14 Jan 2026 19:43:29 +0000</pubDate>
				<category><![CDATA[Software]]></category>
		<guid isPermaLink="false">https://pawaops.com/?p=220</guid>

					<description><![CDATA[System scalability is often associated with performance optimization and fine-tuning. This creates the impression that scalability is only about making systems faster. In reality, scalability is a broader concept focused on how systems grow and adapt to increased demand. This article explains system scalability conceptually, without performance tuning or technical optimization details. Why system scalability ... <a title="Understanding system scalability without performance tuning" class="read-more" href="https://pawaops.com/understanding-system-scalability-without-performance-tuning/" aria-label="Read more about Understanding system scalability without performance tuning">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">System scalability is often associated with performance optimization and fine-tuning. This creates the impression that scalability is only about making systems faster. In reality, scalability is a broader concept focused on how systems grow and adapt to increased demand.</p>



<p class="wp-block-paragraph">This article explains system scalability conceptually, without performance tuning or technical optimization details.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Why system scalability exists</h2>



<p class="wp-block-paragraph">System scalability exists because demand changes over time.</p>



<p class="wp-block-paragraph">A system that works well for a small number of users may struggle as usage increases. Scalability addresses how a system can handle growth without fundamental redesign.</p>



<p class="wp-block-paragraph">The goal is adaptability rather than maximum speed.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">The problem scalability is trying to solve</h2>



<p class="wp-block-paragraph">Without scalability, systems reach limits quickly.</p>



<p class="wp-block-paragraph">As usage grows, systems may become slow, unstable, or unreliable. Fixing these issues late often requires major changes.</p>



<p class="wp-block-paragraph">Scalability aims to delay or reduce these breaking points by designing systems that can grow incrementally.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">How scalability works conceptually</h2>



<p class="wp-block-paragraph">Conceptually, scalability is about adding capacity.</p>



<p class="wp-block-paragraph">Instead of pushing a single component harder, scalable systems allow capacity to be increased by adding more resources or spreading work.</p>



<p class="wp-block-paragraph">This approach reduces dependency on any single component and allows growth to happen gradually.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Why growth patterns matter more than speed</h2>



<p class="wp-block-paragraph">Scalability focuses on behavior under growth rather than peak performance.</p>



<p class="wp-block-paragraph">A system that performs well at small scale may fail under load if growth is not handled correctly. Understanding growth patterns helps anticipate stress points.</p>



<p class="wp-block-paragraph">Scalability prioritizes predictable behavior over raw performance gains.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">What scalability does not guarantee</h2>



<p class="wp-block-paragraph">Scalability does not guarantee efficiency.</p>



<p class="wp-block-paragraph">A scalable system can still be expensive or wasteful. It also does not automatically improve user experience if design choices are poor.</p>



<p class="wp-block-paragraph">Scalability provides flexibility, not perfection.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Common misunderstandings</h2>



<p class="wp-block-paragraph">A common misunderstanding is equating scalability with optimization. Optimization improves efficiency, while scalability improves capacity handling.</p>



<p class="wp-block-paragraph">Another misconception is believing scalability only matters at very large scale. Planning for growth is useful even in small systems.</p>



<p class="wp-block-paragraph">Some people also assume scalability eliminates limits. In reality, it manages limits rather than removing them.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">When scalability actually matters</h2>



<p class="wp-block-paragraph">Scalability matters when growth is expected or uncertain.</p>



<p class="wp-block-paragraph">It becomes especially important for public-facing systems or services that may experience sudden demand changes.</p>



<p class="wp-block-paragraph">For stable, low-usage systems, scalability may be less critical. Its importance increases with unpredictability.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">System scalability exists to help systems adapt to growth without constant redesign. It focuses on handling increased demand rather than maximizing performance.</p>



<p class="wp-block-paragraph">By understanding scalability conceptually, teams can plan for growth without diving into optimization details. A clear mental model helps guide design decisions and manage expectations over time.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://pawaops.com/understanding-system-scalability-without-performance-tuning/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Understanding deployment strategies without pipelines</title>
		<link>https://pawaops.com/understanding-deployment-strategies-without-pipelines/</link>
					<comments>https://pawaops.com/understanding-deployment-strategies-without-pipelines/#respond</comments>
		
		<dc:creator><![CDATA[Admin Pawaops]]></dc:creator>
		<pubDate>Wed, 14 Jan 2026 19:42:29 +0000</pubDate>
				<category><![CDATA[Software]]></category>
		<guid isPermaLink="false">https://pawaops.com/?p=217</guid>

					<description><![CDATA[Deployment strategies are often explained through automated pipelines and complex tooling. This makes the concept feel inaccessible to people who simply want to understand how software is released safely. In reality, deployment strategies describe ideas, not tools. This article explains deployment strategies conceptually, without pipelines or technical implementation details. Why deployment strategies exist Deployment strategies ... <a title="Understanding deployment strategies without pipelines" class="read-more" href="https://pawaops.com/understanding-deployment-strategies-without-pipelines/" aria-label="Read more about Understanding deployment strategies without pipelines">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Deployment strategies are often explained through automated pipelines and complex tooling. This makes the concept feel inaccessible to people who simply want to understand how software is released safely. In reality, deployment strategies describe ideas, not tools.</p>



<p class="wp-block-paragraph">This article explains deployment strategies conceptually, without pipelines or technical implementation details.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Why deployment strategies exist</h2>



<p class="wp-block-paragraph">Deployment strategies exist to reduce risk when releasing changes.</p>



<p class="wp-block-paragraph">Releasing software always involves uncertainty. New changes may introduce errors or unexpected behavior. Deployment strategies provide structured ways to introduce changes gradually and safely.</p>



<p class="wp-block-paragraph">The goal is control, not speed.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">The problem deployment strategies are trying to solve</h2>



<p class="wp-block-paragraph">Without a strategy, deployments become disruptive events.</p>



<p class="wp-block-paragraph">A single mistake can affect all users at once. Recovering from failures may require urgent intervention.</p>



<p class="wp-block-paragraph">Deployment strategies reduce the impact of failures by controlling how and when changes are introduced.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">How deployment strategies work conceptually</h2>



<p class="wp-block-paragraph">Conceptually, deployment strategies manage exposure.</p>



<p class="wp-block-paragraph">Instead of making a change visible to everyone immediately, strategies limit who sees the change and when. This allows teams to observe behavior and react if issues arise.</p>



<p class="wp-block-paragraph">The focus is on managing change, not automating steps.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Why gradual change matters</h2>



<p class="wp-block-paragraph">Gradual change reduces uncertainty.</p>



<p class="wp-block-paragraph">By introducing changes step by step, teams can detect issues early and limit their scope. This makes systems more resilient to unexpected behavior.</p>



<p class="wp-block-paragraph">Gradual approaches prioritize stability over speed.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">What deployment strategies do not guarantee</h2>



<p class="wp-block-paragraph">Deployment strategies do not eliminate the possibility of errors.</p>



<p class="wp-block-paragraph">They do not replace testing or validation. A poorly designed change can still cause problems.</p>



<p class="wp-block-paragraph">Strategies reduce risk, but they do not remove it entirely.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Common misunderstandings</h2>



<p class="wp-block-paragraph">A common misunderstanding is believing deployment strategies require automation. While automation helps, strategies exist independently of tooling.</p>



<p class="wp-block-paragraph">Another misconception is assuming strategies slow down development. In practice, they often increase confidence and efficiency.</p>



<p class="wp-block-paragraph">Some people also believe deployment strategies are only for large systems. Even small systems benefit from controlled releases.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">When deployment strategies actually matter</h2>



<p class="wp-block-paragraph">Deployment strategies matter most when changes affect users directly.</p>



<p class="wp-block-paragraph">They are especially important when systems are used continuously or cannot tolerate downtime.</p>



<p class="wp-block-paragraph">For low-impact changes, strategies may be informal. Their importance grows with risk.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">Deployment strategies exist to manage the risk of releasing changes. They focus on controlling exposure rather than on specific tools or pipelines.</p>



<p class="wp-block-paragraph">By understanding deployment strategies conceptually, teams can apply these ideas in many contexts. A clear mental model helps guide release decisions without unnecessary complexity.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://pawaops.com/understanding-deployment-strategies-without-pipelines/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Understanding Git workflows without strict methodologies</title>
		<link>https://pawaops.com/understanding-git-workflows-without-strict-methodologies/</link>
					<comments>https://pawaops.com/understanding-git-workflows-without-strict-methodologies/#respond</comments>
		
		<dc:creator><![CDATA[Admin Pawaops]]></dc:creator>
		<pubDate>Wed, 14 Jan 2026 19:40:43 +0000</pubDate>
				<category><![CDATA[Software]]></category>
		<guid isPermaLink="false">https://pawaops.com/?p=215</guid>

					<description><![CDATA[Git workflows are often presented as rigid methodologies that teams must follow precisely. This creates the impression that there is a single correct way to use Git. In practice, workflows are flexible patterns rather than strict rules. This article explains Git workflows conceptually, without enforcing predefined models or methodologies. Why Git workflows exist Git workflows ... <a title="Understanding Git workflows without strict methodologies" class="read-more" href="https://pawaops.com/understanding-git-workflows-without-strict-methodologies/" aria-label="Read more about Understanding Git workflows without strict methodologies">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Git workflows are often presented as rigid methodologies that teams must follow precisely. This creates the impression that there is a single correct way to use Git. In practice, workflows are flexible patterns rather than strict rules.</p>



<p class="wp-block-paragraph">This article explains Git workflows conceptually, without enforcing predefined models or methodologies.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Why Git workflows exist</h2>



<p class="wp-block-paragraph">Git workflows exist to coordinate collaboration.</p>



<p class="wp-block-paragraph">When multiple people work on the same codebase, changes must be organized to avoid conflicts and confusion. Workflows provide structure for how changes are introduced and reviewed.</p>



<p class="wp-block-paragraph">The goal is clarity, not restriction.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">The problem workflows are trying to solve</h2>



<p class="wp-block-paragraph">Without a workflow, changes can be applied inconsistently. This can lead to conflicts, lost work, or unclear ownership.</p>



<p class="wp-block-paragraph">Git workflows help teams agree on how work moves from individual contributions to shared code. They reduce ambiguity around collaboration.</p>



<p class="wp-block-paragraph">Workflows are social agreements supported by tools.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">How Git workflows work conceptually</h2>



<p class="wp-block-paragraph">Conceptually, a Git workflow defines how changes flow.</p>



<p class="wp-block-paragraph">Changes start as local work, then move through review, integration, and release stages. The workflow defines when and how these transitions occur.</p>



<p class="wp-block-paragraph">Git itself does not enforce workflows. Teams apply structure on top of Git’s basic capabilities.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Why flexibility matters</h2>



<p class="wp-block-paragraph">Different teams have different needs.</p>



<p class="wp-block-paragraph">Small teams may prefer simple workflows with minimal steps. Larger teams may need more structure to manage coordination.</p>



<p class="wp-block-paragraph">Flexibility allows workflows to evolve as teams and projects change.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">What workflows do not replace</h2>



<p class="wp-block-paragraph">Git workflows do not replace communication or good practices.</p>



<p class="wp-block-paragraph">They do not guarantee code quality or prevent mistakes. Workflows support collaboration but do not enforce correctness.</p>



<p class="wp-block-paragraph">Understanding this prevents overreliance on process.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Common misunderstandings</h2>



<p class="wp-block-paragraph">A common misunderstanding is believing one workflow fits all teams. In reality, workflows are context-dependent.</p>



<p class="wp-block-paragraph">Another misconception is assuming workflows must be complex to be effective. Simpler workflows often work better.</p>



<p class="wp-block-paragraph">Some people also believe workflows are technical constraints. They are primarily agreements between people.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">When Git workflows actually matter</h2>



<p class="wp-block-paragraph">Workflows matter most when collaboration increases.</p>



<p class="wp-block-paragraph">As more people contribute, structure becomes necessary to maintain clarity. Understanding workflows conceptually helps teams adapt rather than rigidly adopt models.</p>



<p class="wp-block-paragraph">For individual projects, workflows may remain informal.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">Git workflows exist to organize collaboration, not to enforce rigid methodologies. They define how changes move through a system rather than dictating how work must be done.</p>



<p class="wp-block-paragraph">By understanding workflows conceptually, teams can design processes that fit their needs. A flexible mental model encourages collaboration without unnecessary constraints.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://pawaops.com/understanding-git-workflows-without-strict-methodologies/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Understanding orchestration vs automation without buzzwords</title>
		<link>https://pawaops.com/understanding-orchestration-vs-automation-without-buzzwords/</link>
					<comments>https://pawaops.com/understanding-orchestration-vs-automation-without-buzzwords/#respond</comments>
		
		<dc:creator><![CDATA[Admin Pawaops]]></dc:creator>
		<pubDate>Wed, 14 Jan 2026 19:39:47 +0000</pubDate>
				<category><![CDATA[Software]]></category>
		<guid isPermaLink="false">https://pawaops.com/?p=212</guid>

					<description><![CDATA[Orchestration and automation are often used interchangeably, which leads to confusion. Many explanations rely on industry buzzwords rather than clarifying what these terms actually mean. As a result, people struggle to understand how they differ and when each concept applies. This article explains orchestration and automation conceptually, without buzzwords or technical implementation details. Why these ... <a title="Understanding orchestration vs automation without buzzwords" class="read-more" href="https://pawaops.com/understanding-orchestration-vs-automation-without-buzzwords/" aria-label="Read more about Understanding orchestration vs automation without buzzwords">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Orchestration and automation are often used interchangeably, which leads to confusion. Many explanations rely on industry buzzwords rather than clarifying what these terms actually mean. As a result, people struggle to understand how they differ and when each concept applies.</p>



<p class="wp-block-paragraph">This article explains orchestration and automation conceptually, without buzzwords or technical implementation details.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Why these concepts exist</h2>



<p class="wp-block-paragraph">Both orchestration and automation exist to reduce manual effort.</p>



<p class="wp-block-paragraph">As systems grow, repeating tasks manually becomes inefficient and error-prone. Automation and orchestration introduce structured ways to handle repetitive work.</p>



<p class="wp-block-paragraph">The difference lies not in what they replace, but in how they coordinate actions.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">The problem automation is trying to solve</h2>



<p class="wp-block-paragraph">Automation focuses on individual tasks.</p>



<p class="wp-block-paragraph">It answers the question: how can a specific action be performed automatically instead of manually? This could involve running a script, triggering a process, or applying a change.</p>



<p class="wp-block-paragraph">Automation reduces human involvement at the task level.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">The problem orchestration is trying to solve</h2>



<p class="wp-block-paragraph">Orchestration focuses on coordination.</p>



<p class="wp-block-paragraph">It answers the question: how do multiple automated tasks work together in the correct order? Orchestration ensures that actions happen in sequence, with awareness of dependencies.</p>



<p class="wp-block-paragraph">It manages the flow between automated steps rather than the steps themselves.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">How automation and orchestration work conceptually</h2>



<p class="wp-block-paragraph">Automation operates at a local level. It performs a defined action when triggered.</p>



<p class="wp-block-paragraph">Orchestration operates at a higher level. It decides when and how different automated actions should interact.</p>



<p class="wp-block-paragraph">Automation executes tasks. Orchestration coordinates them.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Why the distinction matters</h2>



<p class="wp-block-paragraph">Confusing orchestration with automation can lead to poor system design.</p>



<p class="wp-block-paragraph">Relying only on automation can result in disconnected actions without coordination. Relying only on orchestration without proper automation creates fragile workflows.</p>



<p class="wp-block-paragraph">Understanding the distinction helps teams design systems that are both efficient and reliable.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">What these concepts do not guarantee</h2>



<p class="wp-block-paragraph">Neither automation nor orchestration guarantees good outcomes by themselves.</p>



<p class="wp-block-paragraph">They do not replace clear logic, proper planning, or system understanding. Poorly designed automation can amplify mistakes.</p>



<p class="wp-block-paragraph">Recognizing their limits prevents overconfidence in tooling.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Common misunderstandings</h2>



<p class="wp-block-paragraph">A common misunderstanding is believing orchestration is just advanced automation. In reality, it serves a different purpose.</p>



<p class="wp-block-paragraph">Another misconception is assuming automation eliminates the need for oversight. Automated systems still require monitoring and adjustment.</p>



<p class="wp-block-paragraph">Some people also believe these concepts only apply to large systems. They are useful at many scales.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">When orchestration versus automation actually matters</h2>



<p class="wp-block-paragraph">The difference becomes important when systems involve multiple dependent steps.</p>



<p class="wp-block-paragraph">Simple tasks benefit from automation alone. Complex workflows benefit from orchestration.</p>



<p class="wp-block-paragraph">Understanding when to apply each concept helps avoid unnecessary complexity.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">Automation and orchestration both reduce manual work, but they address different problems. Automation handles individual tasks, while orchestration coordinates how tasks work together.</p>



<p class="wp-block-paragraph">By understanding these concepts without buzzwords, it becomes easier to reason about system behavior. A clear mental model helps teams apply the right approach for their needs.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://pawaops.com/understanding-orchestration-vs-automation-without-buzzwords/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
