Keep the shared guards in one module instead of one copy per file

`hasOwnKey`, `isUnsafeObjectKey`, `copyObject` and `shellQuote` were written out
in full in up to seven files each. AGENTS.md called that legitimate because QML
and JS files share no function scope. The scoping constraint is real, but it
only forces each file to declare the name; it does not force each file to carry
the body. The repo already crosses every surface, config pages included, with
eleven `.pragma library` modules.

Move the bodies into Guards.js and leave a one-line delegation behind. Call
sites are untouched, which is why the diff is narrow for 124 of them.

Unifying turned up two divergences the copies had been hiding:

* `isUnsafeObjectKey` existed in two forms. main.qml, ProviderNormalizer.js and
  configProviders.qml coerced the key with `String(key || "")` first;
  CostPresentation.js and CommandLedger.js compared the raw value, so a
  non-string key that stringifies to `__proto__` would have slipped past. Those
  two are mine, from the previous refactors. The shared version keeps the
  coercing form.
* configDisplay.qml inlined its own `copyObject` and three bare
  `Object.prototype.hasOwnProperty.call` checks. They carried the right guard,
  so this is a consolidation and not a fix.

tests/tst_guards.qml adds 18 cases for logic that had never been executed by a
test: a payload shadowing `hasOwnProperty`, prototype-polluting keys surviving a
copy, and shell quoting against `$(...)`, backticks, `&&` and a quote breakout.

test_security_regressions.sh keeps requiring the declaration in every calling
file, and now also fails if a guard implementation appears anywhere outside
Guards.js. Verified by re-inlining `hasOwnKey` in ProviderIdentity.js: the check
exits 1 and names the file.
ae87b92dd0Simone committed on 8/19/2026, 1:04:59 PMparent63f5fd6

Checks

All checks passed
13 files changedLine totals unavailable