Skip to content

Daemon: DaemonAggregator and SecurityFilter have no shipped-template guard — on a stock tree --preview renders (sample) entries as the principal's persona and prints Security filter applied: clean #2158

Description

@tzioup

Version: v7.40.4 (be9e8ef889f00a29f4fd677dee4772fdf32e07ce), skills/Daemon v1.0.25.

Every command below runs on a fresh clone of the release tag under env -i, against a tree built by
copying LifeOS/install. Nothing depends on my local install — env -i drops LIFEOS_DIR, which
Tools/DaemonAggregator.ts:38-39 resolves before falling back to $HOME/.claude/LIFEOS.

What the docs say

skills/Daemon/SKILL.md:31 — this skill "aggregates the safe sources, blocks the sensitive ones at
the code level, and gives you a preview-then-approve gate before anything ships."
SKILL.md:131 — "Defense in depth: Aggregator filter + SecurityFilter + pre-commit hook +
manual approval." SKILL.md:132 — "Fail closed: If uncertain, exclude the content."
SKILL.md:151-153 — "## For Community Forks / This skill is designed to be generic."

What is broken

On a stock install with no interview run, DaemonAggregator.ts --preview renders the shipped TELOS
template as the principal's public persona: (sample) bullets under [MISSION], [TELOS] and
[FAVORITE_BOOKS]; the (interview — …) stub under [ABOUT]; and under [WISDOM], the source
file's literal YAML frontmatter block plus the template's own "SAMPLE TEMPLATE" warning banner, each
as a wisdom quote. The run reports Security filter applied: clean (no redactions needed), a counts
footer of Books: 6 / Wisdom: 5, and exits 0.

--output writes the same bytes. Workflows/UpdateDaemon.md:55 is that write; :63 copies the file
to ~/Projects/daemon/public/daemon.md; :71 commits and pushes it. So of the four layers named at
SKILL.md:131, against template content the aggregator filter passes, SecurityFilter reports clean,
DeployGate.ts / deploy.sh are not in the payload, and the human approval step at
UpdateDaemon.md:50 is what remains.

#1960 (closed 2026-09-03) covered the read order — the aggregator took the shipped per-topic files
ahead of TELOS.md — and its close states that the aggregator now reads TELOS.md sections first,
next release. This report is not re-filing that. It is about the tree that ordering change still
produces: TELOS.md ships with the same markers (USER/TELOS/TELOS.md:2 provenance: template,
:26 M0 (sample), :35 G0 (sample)), and nothing in the reader or the filter recognises them.
The frontmatter and banner shapes below come from reading WISDOM.md raw and will move once that
read order lands; the (sample) and (interview shapes do not depend on which file is read.

Where

Readerskills/Daemon/Tools/DaemonAggregator.ts:189-198:

function readWisdom(): string[] {
  const content = readFileIfExists(join(TELOS_DIR, "WISDOM.md")) ?? readTelosSection("Wisdom");
  if (!content) return [];
  // Split by double newlines to get individual quotes
  return content
    .split(/\n{2,}/)
    .map((q) => q.trim())
    .filter((q) => q.length > 10 && !q.startsWith("#"));
}

Two predicates: longer than 10 characters, does not start with #. The frontmatter block starts with
-; the banner and the guidance line start with >. All three pass. readMissions() (:121-141)
and readGoals() (:143-165) select on the M0/G0 ID pattern, which the template entries carry.
readBooks() (:167-176) takes every bullet in the file.

Writerskills/Daemon/Tools/SecurityFilter.ts:21-64 holds five pattern tables: blocked names
(baseline empty), partner aliases, filesystem paths, credentials, internal endpoints. The file
contains no occurrence of sample, template, placeholder, provenance or interview
(rg -ci returns exit 1). filterContent returns passed: redactions.length === 0 (:184), so
"found no private data" and "safe to publish" are one boolean. Its self-test (:304-310) is five
cases, none of them template text.

Reader and writer disagree on nothing here — the reader emits template text and the writer has no
pattern that describes it.

Precedent in the same payloadLIFEOS/TOOLS/ContextAudit.ts:267-271 already carries the
(sample) / (interview patterns, added for #1559 with the comment "unfilled templates carry
(sample) entries and (interview ...) stubs that previously scored a false-clean zero."
LIFEOS/TOOLS/GenerateTelosSummary.ts carries all three guards this output would need:
:145 strips leading YAML frontmatter (#1496), :167 is isPlaceholder (/\(sample\b/i), and
:176-180 is isScaffolding, which rejects horizontal rules, headings, emphasis footnotes, and
blockquote lines — its comment notes "every shipped template writes its guidance banner as >".
Those three shapes are exactly the three in the [WISDOM] output below.

Repro on a clean tree

git clone --depth 1 --branch v7.40.4 https://github.com/danielmiessler/LifeOS
cd LifeOS && git rev-parse HEAD     # be9e8ef889f00a29f4fd677dee4772fdf32e07ce

TREE="$(mktemp -d)"
cp -R LifeOS/install "$TREE/.claude"
ln -s "$TREE/.claude/USER" "$TREE/.claude/LIFEOS/USER"   # what InstallEngine.ts:577-578 creates

env -i PATH="$PATH" HOME="$TREE" \
  bun "$TREE/.claude/skills/Daemon/Tools/DaemonAggregator.ts" --preview --verbose

Step 1 — the sources report OK

$ … --sources
  [+] TELOS/MISSION.md: OK
  [+] TELOS/GOALS.md: OK
  [+] TELOS/BOOKS.md: OK
  [+] TELOS/WISDOM.md: OK
  [+] PRINCIPAL_IDENTITY.md: OK

--sources reports existsSync only (:674-679); every OK above is a shipped template.

Step 2 — --preview output

Verbatim, with two whole sections elided where marked ([TELOS] is a byte-for-byte repeat of
[MISSION]; [FAVORITE_BOOKS] is six more (sample) bullets of the same shape). Nothing inside a
quoted section is trimmed.

Aggregating LifeOS data sources...

Security filter applied: clean (no redactions needed)

--- PREVIEW ---

# DAEMON DATA FILE

# This file contains personal information for the daemon profile
# Format: Section headers are marked with [SECTION_NAME]
# Auto-generated by DaemonAggregator from LifeOS sources

[ABOUT]

(interview — what you work on day-to-day)

[CURRENT_LOCATION]



[MISSION]

**M0:** (sample) Help the people I care about live better — clearer thinking, more agency, fewer needless obstacles.
**M1:** (sample) Leave my field, my community, or my craft a little better than I found it.

**G0:** (sample) Ship MVP of Project X by 2026-Q2 — measurable: 100 daily active users.
**G1:** (sample) Publish 24 newsletter issues this year — measurable: one every other Sunday, no skipped weeks.

        [ … [TELOS] and [FAVORITE_BOOKS] elided — see note above … ]

[FAVORITE_MOVIES]


[WISDOM]

- ---
provenance: template
---
- > 🎯 SAMPLE TEMPLATE — This file shows the SHAPE of your TELOS data. Every entry below is a placeholder. Run `/interview` (or talk to your DA) to replace these samples with your actual mission, goals, beliefs, etc. Pulse will show populated entries here once you run the interview.
- > Hard-won insights you keep coming back to — the distilled output of years of living.
- - (sample) The thing you're avoiding is usually the thing that would move the needle.
- (sample) Most "decisions" are actually defaults you haven't noticed.
- (sample) Going slowly looks slow until you count how long the rushed version took to fix.
- (sample) If you can't explain it simply, you don't understand it well enough yet.
- One line each. Should read like aphorisms you'd write on an index card. Not clichés — things you've actually earned by going through something.

# Note: PROJECTS are pulled dynamically

--- END PREVIEW ---

Sections populated:
  Books: 6
  Movies: 0
  Ideas: 0
  Work themes: 0
  Wisdom: 5
  Predictions: 0

Exit 0. The first [WISDOM] bullet is the source file's YAML frontmatter block; the second and third
are the template's own warning banner and its guidance blockquote.

Step 3 — the same bytes are what gets written and pushed

$ … --output "$TREE/daemon.md"
Security filter applied: clean (no redactions needed)
Wrote daemon.md to: …/daemon.md

$ grep -c 'provenance: template' "$TREE/daemon.md"   # non-zero
$ grep -c 'SAMPLE TEMPLATE'      "$TREE/daemon.md"   # non-zero

UpdateDaemon.md:63 copies that file into ~/Projects/daemon/public/; :71 pushes it.

Negative control

env -i PATH="$PATH" HOME="$TREE" \
  bun "$TREE/.claude/skills/Daemon/Tools/DaemonAggregator.ts" --preview > preview.txt 2>&1

for pat in '(sample)' 'SAMPLE TEMPLATE' 'provenance: template' '(interview'; do
  grep -qF "$pat" preview.txt && echo "FOUND: $pat" || echo "absent: $pat"
done
grep -qF 'clean (no redactions needed)' preview.txt && echo "FOUND: filter reported clean"

On a populated tree all four content probes print absent:, which is what makes this control
discriminating — and the clean line prints there too, correctly, because there is nothing to redact.
On v7.40.4's stock tree all five print FOUND, clean alongside them:

FOUND: (sample)
FOUND: SAMPLE TEMPLATE
FOUND: provenance: template
FOUND: (interview
FOUND: filter reported clean

Layout-independent: the same five FOUND lines appear on a tree where LIFEOS/USER is a real
directory instead of a symlink.

Suggested fix — options, untested

I have not implemented or tested any of these.

  1. Reuse the existing guards. GenerateTelosSummary.ts:145/167/176-180 already implements
    frontmatter stripping, isPlaceholder, and isScaffolding — the three shapes in the [WISDOM]
    output. Lifting them into a shared module and calling them from DaemonAggregator's readers would
    close all three in one place, and keeps one definition of "template text" for both pipelines.
  2. Add template patterns to SecurityFilter. Smallest diff, and consistent with
    SKILL.md:129's "code-level enforcement" framing: a template redaction type over
    /\(sample\b/i, /\(interview\b/i, /^---\n[\s\S]*?\n---/, and leading-> blockquote lines.
    Trade-off: redacting rather than excluding turns each into [REDACTED] in the published output;
    excluding at the reader is cleaner.
  3. Gate on provenance: template. Every shipped TELOS file carries it in frontmatter. A reader
    that skips any source whose frontmatter says provenance: template would zero out all of
    [MISSION], [TELOS], [FAVORITE_BOOKS] and [WISDOM] on a stock tree, which matches
    SKILL.md:132's "fail closed". Coarser than (1) and it would not help a half-filled file whose
    frontmatter was updated.
  4. Independent of 1-3: --preview and --output could exit non-zero when any section resolves
    entirely to template text, so the approval step at UpdateDaemon.md:50 is presented with a
    failure rather than clean (no redactions needed).

Scope — not verified

  • I did not run /interview or /LifeOS setup, so I have not seen what they leave behind.
  • I could not verify behaviour after Where should TELOS content live? The docs and the tools disagree #1960's fix, which is unreleased. My reading that the filter
    still prints clean afterwards is an inference from its close text, which lists three changes and
    no template-text guard.
  • I did not deploy anything. DeployGate.ts and deploy.sh are not in the payload
    (find … -name 'DeployGate*' → 0 hits), and SKILL.md:198 describes the deploy side as
    maintainer-side, so I am not filing that as a defect — it matters only because SKILL.md:131
    counts it as one of four layers and UpdateDaemon.md:58-72 does not invoke it.
  • I make no claim about whether a reviewer would catch (sample) at the approval step. The word is
    visible in the preview. The claim is about what the automated layers report.
  • Inference, marked as such: that a half-populated install would carry only its untouched sections into
    the preview. It follows from the reader's per-file selection described above, but I tested an
    all-template tree, not a mid-interview one.

Confirmations

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions