Context
Tracker for security audit findings deferred from the 2026-05 review pass. Closed audit P1s for the user-facing CLI surface in #106 + #107:
- PII / real-name / personal-domain exposure
.env world-readable Discord token
- SSRF guard in
validateBaseURL (IMDS / RFC 1918)
--api-key argv leak (CWE-214) → added --api-key-file / PATINA_API_KEY_FILE
- systemd unit hardening directives (template)
- Discord allowlist secure-by-default (
RUNTIME_ENFORCE_ALLOWLIST flipped to true)
Items in this issue affect the bot subsystems (ops/component-bridge.mjs, ops/harness.sh, marketing runtime, patina-max) and only matter when those paths are actively used. Maintainer doesn't currently run them, so the items are tracked here to be addressed when (a) the maintainer reactivates the bot, (b) a fork starts using them, or (c) someone wants a clean security posture for future contributors.
Severity ratings combine the two-reviewer audit (Claude security-reviewer + Codex via omc ask).
P1 — High (act before reactivating bot subsystems)
ops/component-bridge.mjs: Discord bot prompt-injection trust boundary
- Files:
ops/component-bridge.mjs:168, ops/runtime-bootstrap.sh:152, ops/runtime-bootstrap.sh:159
allowBots=true, requireMention=false, allowlist is opt-in. A compromised or low-trust bot in the configured channel can prompt-inject the patina runtime via component messages.
- Fix: per-bot-id allowlist for forwarded messages; strip control characters from extracted payloads before injecting into the runtime prompt.
ops/marketing-runtime-bootstrap.sh: marketing runtime not isolated from primary auth
- Files:
ops/marketing-runtime-bootstrap.sh:134, 166, 181
- Bootstrap copies
auth-profiles.json, models.json, and auth/models from the primary OpenClaw config into the marketing profile. A marketing-channel prompt injection therefore acts with primary-account auth material.
- Fix: derive a separate auth profile rather than copying primary; or document the trust assumption explicitly.
patina-max/SKILL.md: MAX Codex dispatch lacks the standalone backend sandbox
- Files:
patina-max/SKILL.md:230, 262
- Standalone backend uses
mkdtempSync + --sandbox read-only + stdin. MAX docs run codex exec without -C or sandbox; a malicious custom pattern/profile/input can become an agent prompt with repo/home access.
- Fix: align MAX dispatch with the standalone backend's sandbox setup.
P2 — Medium
patina-max/SKILL.md: Gemini MAX dispatch via argv
- Files:
patina-max/SKILL.md:200, 256, .patina.default.yaml:64
- Docs say all providers receive stdin, but Gemini uses
gemini -p "$(cat file)". Sensitive input lands in /proc/<pid>/cmdline.
- Fix: use stdin for Gemini, matching the documented contract.
ops/harness.sh: gh pr create --title with unsanitized LLM-produced string
- Files:
ops/harness.sh:479, 483
pr_title is parsed from $GENERATOR_RESULT. A manipulated title could include markdown that triggers downstream automation, or @-mentions, or escape characters.
- Fix: length-cap to ≤120 chars and strip control characters before passing to
gh.
ops/harness.sh: git apply of LLM-generated diff (supply-chain risk)
- Files:
ops/harness.sh:451-453
- Diff is applied to a real git tree, then committed and pushed. A prompt-injection against the planner/generator (via
gh issue list output at ops/harness.sh:226) could land a backdoored postinstall hook in package.json, a .github/workflows/ workflow, or a bin/ script.
- Fix: path allowlist (deny
.github/, package.json scripts, bin/, install.sh); require human review before gh pr merge --auto. AUTO_MERGE=false default mitigates today.
Bootstrap scripts echo Discord channel/guild IDs
- Files:
ops/marketing-runtime-bootstrap.sh:339-342, ops/runtime-bootstrap.sh:189-191
- Channel IDs alone aren't credentials, but combined with a leaked bot token they let an attacker locate the targeted guild without needing OAuth introspection.
- Fix: print only the agent name; gate channel/guild echoes behind
--verbose.
src/security.js: applyInsecureBaseURLOptIn mutates process.env
- Files:
src/security.js:53-63
- Sticky env mutation leaks across calls if patina is ever embedded as a library.
- Fix: pass the flag explicitly through
validateBaseURL (already accepts { allowInsecure }); remove the env-mutation path.
P3 — Low / Info
ops/runtime-bootstrap.sh:91-94 and ops/marketing-runtime-bootstrap.sh:235-267: Discord token via python3 argv
- Token lands in
/proc/<pid>/cmdline for the python process lifetime. Same-user ps window can read it.
- Fix: pass via stdin or env var consumed inside the python heredoc.
ops/component-bridge.mjs:204, 210 and ops/harness.sh:189: large prompts via argv to runtime CLI
- Local same-user process inspection can see Discord/component messages or assembled bot prompts.
- Fix: pipe via stdin where the runtime CLI supports it.
src/api.js:42-43: errorText echoed verbatim in thrown error
- Theoretical: a misconfigured proxy reflecting Authorization header in its error body would surface the token in stderr.
- Fix: truncate
errorText to ~512 chars and redact Bearer\s+\S+ substrings.
ops/component-bridge.mjs:11-35: .env parser doesn't decode escapes inside quoted values
- Functionally fine for Discord tokens (alphanumeric); document the constraint or switch to
dotenv.
Out of scope (won't fix unless requested)
Git history rewrite (git filter-repo)
Context
Tracker for security audit findings deferred from the 2026-05 review pass. Closed audit P1s for the user-facing CLI surface in #106 + #107:
.envworld-readable Discord tokenvalidateBaseURL(IMDS / RFC 1918)--api-keyargv leak (CWE-214) → added--api-key-file/PATINA_API_KEY_FILERUNTIME_ENFORCE_ALLOWLISTflipped totrue)Items in this issue affect the bot subsystems (
ops/component-bridge.mjs,ops/harness.sh, marketing runtime,patina-max) and only matter when those paths are actively used. Maintainer doesn't currently run them, so the items are tracked here to be addressed when (a) the maintainer reactivates the bot, (b) a fork starts using them, or (c) someone wants a clean security posture for future contributors.Severity ratings combine the two-reviewer audit (Claude
security-reviewer+ Codex viaomc ask).P1 — High (act before reactivating bot subsystems)
ops/component-bridge.mjs: Discord bot prompt-injection trust boundaryops/component-bridge.mjs:168,ops/runtime-bootstrap.sh:152,ops/runtime-bootstrap.sh:159allowBots=true,requireMention=false, allowlist is opt-in. A compromised or low-trust bot in the configured channel can prompt-inject the patina runtime via component messages.ops/marketing-runtime-bootstrap.sh: marketing runtime not isolated from primary authops/marketing-runtime-bootstrap.sh:134, 166, 181auth-profiles.json,models.json, andauth/modelsfrom the primary OpenClaw config into the marketing profile. A marketing-channel prompt injection therefore acts with primary-account auth material.patina-max/SKILL.md: MAX Codex dispatch lacks the standalone backend sandboxpatina-max/SKILL.md:230, 262mkdtempSync+--sandbox read-only+ stdin. MAX docs runcodex execwithout-Cor sandbox; a malicious custom pattern/profile/input can become an agent prompt with repo/home access.P2 — Medium
patina-max/SKILL.md: Gemini MAX dispatch via argvpatina-max/SKILL.md:200, 256,.patina.default.yaml:64gemini -p "$(cat file)". Sensitive input lands in/proc/<pid>/cmdline.ops/harness.sh:gh pr create --titlewith unsanitized LLM-produced stringops/harness.sh:479, 483pr_titleis parsed from$GENERATOR_RESULT. A manipulated title could include markdown that triggers downstream automation, or@-mentions, or escape characters.gh.ops/harness.sh:git applyof LLM-generated diff (supply-chain risk)ops/harness.sh:451-453gh issue listoutput atops/harness.sh:226) could land a backdoored postinstall hook inpackage.json, a.github/workflows/workflow, or abin/script..github/,package.jsonscripts,bin/,install.sh); require human review beforegh pr merge --auto.AUTO_MERGE=falsedefault mitigates today.Bootstrap scripts echo Discord channel/guild IDs
ops/marketing-runtime-bootstrap.sh:339-342,ops/runtime-bootstrap.sh:189-191--verbose.src/security.js:applyInsecureBaseURLOptInmutatesprocess.envsrc/security.js:53-63validateBaseURL(already accepts{ allowInsecure }); remove the env-mutation path.P3 — Low / Info
ops/runtime-bootstrap.sh:91-94andops/marketing-runtime-bootstrap.sh:235-267: Discord token via python3 argv/proc/<pid>/cmdlinefor the python process lifetime. Same-userpswindow can read it.ops/component-bridge.mjs:204, 210andops/harness.sh:189: large prompts via argv to runtime CLIsrc/api.js:42-43:errorTextechoed verbatim in thrown errorerrorTextto ~512 chars and redactBearer\s+\S+substrings.ops/component-bridge.mjs:11-35:.envparser doesn't decode escapes inside quoted valuesdotenv.Out of scope (won't fix unless requested)
Git history rewrite (
git filter-repo)noul.krwork email and the three historical author identities (Sangwoo Ha / Hako / Calvin Ha) remain in commit metadata..mailmap(added in security(pr1): scrub maintainer PII, parameterize ops paths, refresh lockfile #106) covers UI display non-destructively. A history rewrite would invalidate every PR/fork SHA and is only worth it if blob-level scrubbing is genuinely required.