Skip to content

Develop#3

Open
xiami303 wants to merge 2 commits into
mainfrom
develop
Open

Develop#3
xiami303 wants to merge 2 commits into
mainfrom
develop

Conversation

@xiami303

@xiami303 xiami303 commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

Release Notes

  • New Features

    • Added automated AI-powered blog post generation pipeline with daily execution via GitHub Actions
    • Integrated multiple AI provider support (Anthropic, OpenAI, or dry-run mode)
    • New npm run generate command to create articles from memory notes locally
    • Automated pull request creation with generated posts to the develop branch
  • Documentation

    • Updated setup guides and roadmap to reflect AI generation workflow

123 and others added 2 commits June 12, 2026 07:21
- Run marked output through sanitize-html so AI-generated/untrusted post
  Markdown can't inject <script>, event handlers, or javascript: URLs
  (verified: script/onerror/javascript: stripped, normal content kept).
- Replace the inline one-liner serve script with scripts/serve.mjs
  (readable, adds path-traversal guard and proper MIME types); drop the
  dead s.writeCode?0:0 expression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- scripts/generate.mjs: read recent memory/ notes, distill into one post,
  write to posts/ with front matter (slug from title, date fallback).
- scripts/providers/: pluggable backends selected by env —
  anthropic (Claude, claude-opus-4-8, adaptive thinking), openai, and a
  zero-dependency dryrun stub so the pipeline runs with no API key.
  SDKs are optionalDependencies, imported dynamically.
- memory/ with an example note as the input source.
- .github/workflows/generate.yml: scheduled/manual generate -> PR to
  develop for human review (never publishes directly).
- npm run generate; README + AGENTS document the memory->AI->PR flow.
- Verified end to end: dryrun generate -> build renders the post; missing
  ANTHROPIC_API_KEY errors clearly instead of failing silently.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xiami303

Copy link
Copy Markdown
Owner Author

@codex review

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a complete AI-powered blog post generation pipeline to OpenBlog. A new CLI (scripts/generate.mjs) reads recent notes from memory/, invokes a pluggable provider (Anthropic, OpenAI, or dryrun), and writes the result to posts/. A daily GitHub Actions workflow runs the pipeline and opens a PR against develop. HTML sanitization is added to the build step, and a static dev server replaces the inline script.

Changes

AI Post Generation Pipeline

Layer / File(s) Summary
Shared prompt scaffolding and response parsing
scripts/providers/prompt.mjs
Defines SYSTEM_PROMPT, OUTPUT_INSTRUCTION, buildUserPrompt, and parseResult — the shared contract consumed by all provider backends.
Provider implementations and selector
scripts/providers/dryrun.mjs, scripts/providers/anthropic.mjs, scripts/providers/openai.mjs, scripts/providers/index.mjs
Implements dryrun (no-network), Anthropic (Claude streaming), and OpenAI (JSON mode) provider modules, each exporting generate({notes, date}). The index module resolves the active provider from environment variables.
generate.mjs orchestrator CLI
scripts/generate.mjs, memory/2026-06-12-example.md
New CLI parses flags (--date, --provider, --days, --max, --force), scans and filters memory/ notes via gray-matter, calls the resolved provider, derives a unique output filename, and writes the post to posts/. Includes an example memory entry.
HTML sanitization in build and static dev server
scripts/build.mjs, scripts/serve.mjs, package.json
build.mjs adds a renderMarkdown helper using sanitize-html with an explicit allowlist before embedding HTML. serve.mjs is a new minimal static server with path-traversal protection. package.json updates serve and adds generate scripts plus sanitize-html dep and optional AI SDK deps.
GitHub Actions daily generation workflow
.github/workflows/generate.yml
Adds generate.yml triggered on a daily cron and manual dispatch; conditionally installs AI SDKs based on secrets, runs generate.mjs, and creates a labeled PR against develop for changes under posts/**.
Docs updates
README.md, AGENTS.md
Updates README roadmap, generation usage section, and directory structure. Updates AGENTS.md with repo layout, new generate command, and AI generation section.

Sequence Diagram(s)

sequenceDiagram
    participant Cron as Schedule / Manual Dispatch
    participant GHA as GitHub Actions
    participant Gen as scripts/generate.mjs
    participant Memory as memory/*.md
    participant Provider as AI Provider (Anthropic/OpenAI/dryrun)
    participant Posts as posts/*.md
    participant PR as Pull Request (develop)

    Cron->>GHA: trigger generate.yml
    GHA->>GHA: npm ci + conditional SDK install
    GHA->>Gen: node scripts/generate.mjs --date YYYY-MM-DD
    Gen->>Memory: scan + parse front matter, filter by cutoff
    Memory-->>Gen: sorted notes[]
    Gen->>Provider: generate({notes, date})
    Provider-->>Gen: {title, tags, summary, body}
    Gen->>Posts: write YYYY-MM-DD-slug.md
    GHA->>PR: create-pull-request → develop (label: ai-generated)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • xiami303/OpenBlog#2: Modifies the same scripts/build.mjs static site generator that this PR extends with HTML sanitization, and establishes the Pages publish CI workflow that this PR complements with the generation workflow.

Poem

🐇 Hop, hop! The rabbit taps its paws with glee,
Notes from memory/ flow into verse so free,
Anthropic or OpenAI — pick your muse,
Or dryrun safely, no API keys to lose.
A cron ticks daily, a PR appears bright,
Words from a rabbit, polished just right! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The pull request title 'Develop' is vague and generic, providing no meaningful information about the changeset's actual purpose or scope. Use a descriptive title that summarizes the main change, such as 'Add AI post generation pipeline with memory-based workflow' or 'Implement automated blog post generation from memory notes'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
README.md (1)

148-148: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align the branch guidance with develop.

This sentence says PRs should target main, but the repo policy elsewhere uses develop as the default work branch. Please update the README so contributors don't follow the wrong target. As per coding guidelines, "Default to the 'develop' branch for all work and pushes. Do NOT push or open PRs against main unless the task explicitly says to release/publish."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 148, The README line currently instructs contributors to
submit Pull Requests to `main`, but the repository policy requires PRs to target
`develop` as the default work branch. Update the sentence at line 148 to change
the PR target guidance from `main` to `develop` to align with the coding
guidelines that state all work and PRs should default to `develop` unless the
task explicitly requires a release or publish to `main`.

Source: Learnings

🧹 Nitpick comments (2)
scripts/providers/anthropic.mjs (1)

41-42: The thinking: { type: "adaptive" } parameter and max_tokens: 8000 are both supported in @anthropic-ai/sdk version 0.40.0 for claude-opus-4-8. However, the documentation recommends setting a large max output token budget when using adaptive thinking, and 8000 is significantly below the 128,000 maximum for this model. Consider increasing max_tokens to allow the model sufficient capacity for thinking and response generation when using adaptive thinking mode.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/providers/anthropic.mjs` around lines 41 - 42, The max_tokens value
of 8000 is significantly below the recommended threshold for adaptive thinking
mode in the claude-opus-4-8 model, which supports up to 128,000 tokens. Increase
the max_tokens parameter in the configuration object (currently set to 8000) to
a substantially larger value that provides adequate capacity for both the
model's thinking process and response generation when using the adaptive
thinking mode.
memory/2026-06-12-example.md (1)

8-11: ⚡ Quick win

Split this into one-thought-per-file notes in memory/.

This entry contains multiple separate thoughts in one file; split them into separate memory/*.md notes so each thought is independently ingestible.

As per coding guidelines: memory/*.md: “one note per thought per file” and include date/tags front matter.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@memory/2026-06-12-example.md` around lines 8 - 11, The
memory/2026-06-12-example.md file contains four separate and distinct thoughts
combined into a single note, which violates the one-thought-per-file guideline.
Split each of the four bullet-point thoughts into its own individual .md file in
the memory/ directory (creating 4 new files total). For each new file, include
the appropriate front matter with date and tags metadata as specified in the
memory directory guidelines, and ensure each file contains only a single
cohesive thought with its content.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/generate.yml:
- Around line 33-37: Replace the mutable version tags with full-length commit
SHAs for the three third-party GitHub Actions: actions/checkout@v4,
actions/setup-node@v4, and peter-evans/create-pull-request@v6. For each action,
look up the specific commit SHA for the version being used and replace the tag
(e.g., `@v4` or `@v6`) with the complete commit hash in the format
@<full-commit-sha>. This pins the actions to immutable commit references,
improving supply-chain security and ensuring workflow reproducibility.
- Around line 66-67: The shell substitution syntax $(date -u +%F) in the
commit-message and title inputs to the peter-evans/create-pull-request@v6 action
will not be evaluated because these inputs are not executed in a shell context
and will render literally. Replace the shell substitution with a GitHub Actions
expression instead. You can either use a built-in expression like ${{
github.run_id }} if that meets your needs, or create a preceding step that
generates the date using run with shell syntax, assigns it to an output variable
with id and outputs syntax, and then reference that output in the commit-message
and title inputs using ${{ steps.step_id.outputs.variable_name }} format.
- Around line 33-35: The actions/checkout@v4 action in the workflow is
configured without disabling credential persistence, which leaves the GitHub
token in Git configuration for subsequent steps and can conflict with the
downstream peter-evans/create-pull-request@v6 action that manages its own
authentication. Add the persist-credentials: false parameter to the with section
of the actions/checkout@v4 action to disable this default behavior and reduce
the credential blast radius.

In `@scripts/generate.mjs`:
- Around line 26-37: The parseArgs function accepts command-line arguments for
--days, --max, and --date without any validation, allowing invalid values
(negative numbers, NaN, malformed dates) to silently pass through to filtering
logic. Add validation checks in the parseArgs function after each argument
assignment to ensure: days is a positive integer, max is a positive integer, and
date is a valid date string (if provided). Additionally, verify that the
validated arguments are properly used at lines 54-58 and 98-107 where they are
consumed for filtering and output operations, ensuring these locations respect
the validation constraints or add additional defensive checks if the arguments
are used in different contexts.
- Around line 87-95: The uniquePath function can return an existing file path
when collisions for date-slug through date-slug-99 all exist, causing unintended
overwrites. After the for loop (which iterates from n=2 to n<100) completes
without finding a unique path, the function currently returns the last candidate
value regardless of whether it exists. Fix this by checking if the final
candidate path exists after the loop completes—if it does, either throw an error
indicating too many collisions, or extend the search range beyond 100 to
continue looking for an available filename, ensuring the function never returns
a path that already exists unless the force flag is explicitly set to true.

In `@scripts/serve.mjs`:
- Around line 27-35: The path validation has two security/stability issues: the
`filePath.startsWith(PUBLIC_DIR)` check is vulnerable to prefix-confusion
attacks (e.g., `/publicity` bypasses the guard) and `decodeURIComponent` throws
unhandled exceptions on malformed percent-encoding. To fix this, wrap the
`decodeURIComponent` call in a try-catch block to respond with a 400 Bad Request
on invalid input, and harden the path check by using `resolve()` to normalize
both `filePath` and `PUBLIC_DIR` before comparing, ensuring the resolved
filePath actually starts with the normalized PUBLIC_DIR directory path rather
than just matching the string prefix.

---

Outside diff comments:
In `@README.md`:
- Line 148: The README line currently instructs contributors to submit Pull
Requests to `main`, but the repository policy requires PRs to target `develop`
as the default work branch. Update the sentence at line 148 to change the PR
target guidance from `main` to `develop` to align with the coding guidelines
that state all work and PRs should default to `develop` unless the task
explicitly requires a release or publish to `main`.

---

Nitpick comments:
In `@memory/2026-06-12-example.md`:
- Around line 8-11: The memory/2026-06-12-example.md file contains four separate
and distinct thoughts combined into a single note, which violates the
one-thought-per-file guideline. Split each of the four bullet-point thoughts
into its own individual .md file in the memory/ directory (creating 4 new files
total). For each new file, include the appropriate front matter with date and
tags metadata as specified in the memory directory guidelines, and ensure each
file contains only a single cohesive thought with its content.

In `@scripts/providers/anthropic.mjs`:
- Around line 41-42: The max_tokens value of 8000 is significantly below the
recommended threshold for adaptive thinking mode in the claude-opus-4-8 model,
which supports up to 128,000 tokens. Increase the max_tokens parameter in the
configuration object (currently set to 8000) to a substantially larger value
that provides adequate capacity for both the model's thinking process and
response generation when using the adaptive thinking mode.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 96d89c16-d978-4698-bdc2-cc761eec4a06

📥 Commits

Reviewing files that changed from the base of the PR and between 01cadb2 and cfce959.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (14)
  • .github/workflows/generate.yml
  • AGENTS.md
  • README.md
  • memory/.gitkeep
  • memory/2026-06-12-example.md
  • package.json
  • scripts/build.mjs
  • scripts/generate.mjs
  • scripts/providers/anthropic.mjs
  • scripts/providers/dryrun.mjs
  • scripts/providers/index.mjs
  • scripts/providers/openai.mjs
  • scripts/providers/prompt.mjs
  • scripts/serve.mjs

Comment on lines +33 to +35
- uses: actions/checkout@v4
with:
ref: develop

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find and read the workflow file
find . -name "generate.yml" -o -name "generate.yaml" | head -20

Repository: xiami303/OpenBlog

Length of output: 94


🏁 Script executed:

# Also check the repository structure for workflow files
git ls-files .github/workflows/ 2>/dev/null | head -20

Repository: xiami303/OpenBlog

Length of output: 122


🏁 Script executed:

# Read the full generate.yml workflow file
cat -n .github/workflows/generate.yml

Repository: xiami303/OpenBlog

Length of output: 2999


🌐 Web query:

actions/checkout persist-credentials default behavior GitHub Actions

💡 Result:

The default behavior of the persist-credentials input in the actions/checkout GitHub Action is true [1][2][3]. When set to true (the default), the action automatically configures the local Git repository with the provided GitHub token or SSH key [1][4][3]. This allows your workflow scripts to execute authenticated Git commands (such as git fetch or git push) without requiring manual credential configuration [1][5][6]. For security, the action now stores these credentials in a separate file under $RUNNER_TEMP rather than directly within the.git/config file, and the token is automatically removed during post-job cleanup [1][6]. If you do not want these credentials persisted in the Git configuration, you can opt out by setting persist-credentials to false in your workflow file [1][5][7].

Citations:


🌐 Web query:

peter-evans/create-pull-request credentials persist-credentials security

💡 Result:

When using the peter-evans/create-pull-request action in workflows that also use actions/checkout, you may encounter an HTTP 400 error described as a "Duplicate header: Authorization" [1][2][3]. This occurs because actions/checkout (specifically from version 6 onwards) persists a git credential by default, while peter-evans/create-pull-request also injects its own authentication header [4][2][3]. When Git attempts to push changes, both headers are sent, leading GitHub to reject the request [1][2][3]. To resolve this, the recommended approach is to set persist-credentials to false in your actions/checkout step [1][2][3]. This prevents the checkout action from storing credentials, leaving the peter-evans/create-pull-request action to handle the necessary authentication for the subsequent push [1][2]. Example workflow configuration: - uses: actions/checkout@v6 with: persist-credentials: false - name: Create Pull Request uses: peter-evans/create-pull-request@v8 with: token: ${{ secrets.GITHUB_TOKEN }} While the peter-evans/create-pull-request action has implemented internal measures to attempt to mitigate compatibility issues with actions/checkout@v6 (such as temporarily hiding credential files) [4][5][6], setting persist-credentials: false remains a common and effective practice to ensure reliable authentication and avoid duplicate header conflicts [1][2][3].

Citations:


Disable credential persistence on checkout.

actions/checkout@v4 keeps credentials enabled by default, persisting the GitHub token in Git configuration for subsequent steps. Since this workflow uses peter-evans/create-pull-request@v6 which manages its own authentication, set persist-credentials: false to narrow the credential blast radius and avoid authentication conflicts.

Suggested fix
      - uses: actions/checkout@v4
        with:
          ref: develop
+         persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 33-35: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 33-33: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/generate.yml around lines 33 - 35, The actions/checkout@v4
action in the workflow is configured without disabling credential persistence,
which leaves the GitHub token in Git configuration for subsequent steps and can
conflict with the downstream peter-evans/create-pull-request@v6 action that
manages its own authentication. Add the persist-credentials: false parameter to
the with section of the actions/checkout@v4 action to disable this default
behavior and reduce the credential blast radius.

Sources: MCP tools, Linters/SAST tools

Comment on lines +33 to +37
- uses: actions/checkout@v4
with:
ref: develop

- uses: actions/setup-node@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/generate.yml

Repository: xiami303/OpenBlog

Length of output: 2999


Pin the third-party actions to commit SHAs.

actions/checkout@v4 (line 33), actions/setup-node@v4 (line 37), and peter-evans/create-pull-request@v6 (line 61) use mutable tags. GitHub recommends pinning Actions to full-length commit SHAs to reduce supply-chain risk and ensure workflow reproducibility. (docs.github.com)

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 33-35: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 33-33: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 37-37: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/generate.yml around lines 33 - 37, Replace the mutable
version tags with full-length commit SHAs for the three third-party GitHub
Actions: actions/checkout@v4, actions/setup-node@v4, and
peter-evans/create-pull-request@v6. For each action, look up the specific commit
SHA for the version being used and replace the tag (e.g., `@v4` or `@v6`) with the
complete commit hash in the format @<full-commit-sha>. This pins the actions to
immutable commit references, improving supply-chain security and ensuring
workflow reproducibility.

Sources: MCP tools, Linters/SAST tools

Comment on lines +66 to +67
commit-message: "AI: new post from memory ($(date -u +%F))"
title: "AI draft: new post from memory ($(date -u +%F))"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/generate.yml | head -80 | tail -20

Repository: xiami303/OpenBlog

Length of output: 1059


🌐 Web query:

peter-evans/create-pull-request action shell substitution commit-message title parameter evaluation

💡 Result:

The peter-evans/create-pull-request GitHub action does not perform internal shell substitution on its inputs [1][2]. Instead, it relies on the standard GitHub Actions workflow syntax for processing values before they are passed to the action [1]. To use variables, environment values, or command outputs within the title or commit-message parameters, you must use the ${{} } expression syntax directly in your workflow YAML file [1][3]. Because GitHub Actions evaluates these expressions before the step executes, the resolved values are passed to the action as static strings [1]. For example, to include a dynamic value (such as a date or a git commit hash) in your title or commit-message, you should resolve the value in a preceding step or use workflow context variables: jobs: createPullRequest: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Generate dynamic value id: vars run: echo "my_date=$(date +%Y-%m-%d)" >> $GITHUB_OUTPUT - name: Create Pull Request uses: peter-evans/create-pull-request@v8 with: title: "Update for ${{ steps.vars.outputs.my_date }}" commit-message: "Update for ${{ steps.vars.outputs.my_date }}" In this pattern, the shell substitution occurs in the run step, which then populates the GitHub Actions output variable. The create-pull-request action then receives the finalized string as the value for its inputs [1][2].

Citations:


Replace shell substitution with GitHub Actions expression or output variable.

$(date -u +%F) is shell syntax that only evaluates in run steps. The commit-message and title inputs to peter-evans/create-pull-request@v6 are not executed in a shell, so this will render literally. Use a GitHub Actions expression like ${{ github.run_id }} or generate the date in a preceding step and reference it via ${{ steps.step_id.outputs.variable_name }}.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/generate.yml around lines 66 - 67, The shell substitution
syntax $(date -u +%F) in the commit-message and title inputs to the
peter-evans/create-pull-request@v6 action will not be evaluated because these
inputs are not executed in a shell context and will render literally. Replace
the shell substitution with a GitHub Actions expression instead. You can either
use a built-in expression like ${{ github.run_id }} if that meets your needs, or
create a preceding step that generates the date using run with shell syntax,
assigns it to an output variable with id and outputs syntax, and then reference
that output in the commit-message and title inputs using ${{
steps.step_id.outputs.variable_name }} format.

Source: MCP tools

Comment thread scripts/generate.mjs
Comment on lines +26 to +37
function parseArgs(argv) {
const args = { days: 7, max: 20, force: false };
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a === "--date") args.date = argv[++i];
else if (a === "--provider") args.provider = argv[++i];
else if (a === "--days") args.days = Number(argv[++i]);
else if (a === "--max") args.max = Number(argv[++i]);
else if (a === "--force") args.force = true;
else if (a === "--help" || a === "-h") args.help = true;
}
return args;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate CLI inputs before they reach note filtering.

--days, --max, and --date are used without validation; invalid values (e.g., --max NaN, negative days, bad date) can silently produce incorrect filtering/output behavior.

Suggested fix
 function parseArgs(argv) {
   const args = { days: 7, max: 20, force: false };
+  const takeValue = (flag, iRef) => {
+    const v = argv[iRef + 1];
+    if (!v || v.startsWith("--")) throw new Error(`Missing value for ${flag}`);
+    return v;
+  };
+
   for (let i = 0; i < argv.length; i++) {
     const a = argv[i];
-    if (a === "--date") args.date = argv[++i];
-    else if (a === "--provider") args.provider = argv[++i];
-    else if (a === "--days") args.days = Number(argv[++i]);
-    else if (a === "--max") args.max = Number(argv[++i]);
+    if (a === "--date") args.date = takeValue("--date", i), i++;
+    else if (a === "--provider") args.provider = takeValue("--provider", i), i++;
+    else if (a === "--days") args.days = Number(takeValue("--days", i)), i++;
+    else if (a === "--max") args.max = Number(takeValue("--max", i)), i++;
     else if (a === "--force") args.force = true;
     else if (a === "--help" || a === "-h") args.help = true;
   }
+
+  if (!Number.isInteger(args.days) || args.days < 0) {
+    throw new Error("--days must be an integer >= 0");
+  }
+  if (!Number.isInteger(args.max) || args.max <= 0) {
+    throw new Error("--max must be an integer > 0");
+  }
+  if (args.date && Number.isNaN(new Date(args.date).getTime())) {
+    throw new Error("--date must be YYYY-MM-DD");
+  }
+
   return args;
 }

Also applies to: 54-58, 98-107

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/generate.mjs` around lines 26 - 37, The parseArgs function accepts
command-line arguments for --days, --max, and --date without any validation,
allowing invalid values (negative numbers, NaN, malformed dates) to silently
pass through to filtering logic. Add validation checks in the parseArgs function
after each argument assignment to ensure: days is a positive integer, max is a
positive integer, and date is a valid date string (if provided). Additionally,
verify that the validated arguments are properly used at lines 54-58 and 98-107
where they are consumed for filtering and output operations, ensuring these
locations respect the validation constraints or add additional defensive checks
if the arguments are used in different contexts.

Comment thread scripts/generate.mjs
Comment on lines +87 to +95
async function uniquePath(date, slug, force) {
let candidate = join(POSTS_DIR, `${date}-${slug}.md`);
if (force || !(await exists(candidate))) return candidate;
for (let n = 2; n < 100; n++) {
candidate = join(POSTS_DIR, `${date}-${slug}-${n}.md`);
if (!(await exists(candidate))) return candidate;
}
return candidate;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

uniquePath can overwrite an existing file when collisions exceed the fixed loop.

If date-slug.md through date-slug-99.md already exist, the function returns an existing path and writeFile overwrites it even when --force is not set.

Suggested fix
 async function uniquePath(date, slug, force) {
   let candidate = join(POSTS_DIR, `${date}-${slug}.md`);
   if (force || !(await exists(candidate))) return candidate;
   for (let n = 2; n < 100; n++) {
     candidate = join(POSTS_DIR, `${date}-${slug}-${n}.md`);
     if (!(await exists(candidate))) return candidate;
   }
-  return candidate;
+  throw new Error(
+    `No unique filename available for ${date}-${slug}.md (1..99 exhausted). Use --force to overwrite intentionally.`
+  );
 }

Also applies to: 133-133

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/generate.mjs` around lines 87 - 95, The uniquePath function can
return an existing file path when collisions for date-slug through date-slug-99
all exist, causing unintended overwrites. After the for loop (which iterates
from n=2 to n<100) completes without finding a unique path, the function
currently returns the last candidate value regardless of whether it exists. Fix
this by checking if the final candidate path exists after the loop completes—if
it does, either throw an error indicating too many collisions, or extend the
search range beyond 100 to continue looking for an available filename, ensuring
the function never returns a path that already exists unless the force flag is
explicitly set to true.

Comment thread scripts/serve.mjs
Comment on lines +27 to +35
let urlPath = decodeURIComponent((req.url || "/").split("?")[0]);
if (urlPath.endsWith("/")) urlPath += "index.html";

const filePath = join(PUBLIC_DIR, urlPath);
// Prevent path traversal outside public/.
if (!filePath.startsWith(PUBLIC_DIR)) {
res.statusCode = 403;
return res.end("Forbidden");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
node - <<'NODE'
const path = require("node:path");
const PUBLIC = "/tmp/public";
for (const u of ["/../publicity/probe.txt", "/../etc/passwd", "/index.html"]) {
  const joined = path.join(PUBLIC, u);
  const oldGuard = joined.startsWith(PUBLIC);
  console.log({ u, joined, oldGuard });
}
NODE

Repository: xiami303/OpenBlog

Length of output: 291


🏁 Script executed:

# Check the file location and read the vulnerable code section
find . -name "serve.mjs" -type f

Repository: xiami303/OpenBlog

Length of output: 81


🏁 Script executed:

# Read the serve.mjs file to verify the vulnerable code
head -50 scripts/serve.mjs | cat -n

Repository: xiami303/OpenBlog

Length of output: 1921


🏁 Script executed:

# Test the suggested fix to ensure it properly prevents traversal
node - <<'NODE'
const path = require("node:path");
const PUBLIC = "/tmp/public";

// Test cases
const testCases = [
  "/../publicity/probe.txt",  // Sibling directory attack
  "/../etc/passwd",            // Absolute escape
  "/index.html",               // Normal file
  "/subdir/file.txt",          // Nested file
  "//index.html",              // Double slash
];

console.log("=== CURRENT (VULNERABLE) GUARD ===");
for (const u of testCases) {
  const joined = path.join(PUBLIC, u);
  const oldGuard = joined.startsWith(PUBLIC);
  console.log({ u, path: joined, pass: oldGuard });
}

console.log("\n=== SUGGESTED FIX GUARD ===");
const sep = path.sep;
for (const u of testCases) {
  try {
    const filePath = path.resolve(PUBLIC, `.${u}`);
    const newGuard = (filePath === PUBLIC || filePath.startsWith(PUBLIC + sep));
    console.log({ u, path: filePath, pass: newGuard });
  } catch (e) {
    console.log({ u, error: e.message });
  }
}
NODE

Repository: xiami303/OpenBlog

Length of output: 844


🏁 Script executed:

# Test decodeURIComponent exception handling
node - <<'NODE'
// Test if malformed percent-encoding throws
const testUrls = [
  "/normal/path",
  "/path%20with%20space",
  "/malformed%ZZ",  // Invalid percent encoding
  "/path%",         // Incomplete percent encoding
];

console.log("=== decodeURIComponent BEHAVIOR ===");
for (const url of testUrls) {
  try {
    const decoded = decodeURIComponent(url);
    console.log({ url, decoded, error: null });
  } catch (e) {
    console.log({ url, error: e.message });
  }
}
NODE

Repository: xiami303/OpenBlog

Length of output: 330


Harden path normalization and exception handling (current prefix check is bypassable and decodeURIComponent can throw).

The startsWith(PUBLIC_DIR) check allows prefix-confusion attacks—requests to a sibling directory like /publicity will pass the guard because /tmp/publicity starts with /tmp/public. Additionally, decodeURIComponent throws an exception on malformed percent-encoding (e.g., %ZZ or %), which will crash the request handler since it has no error handler.

Suggested fix
-import { join, extname, dirname } from "node:path";
+import { join, extname, dirname, resolve, sep } from "node:path";

 const server = createServer(async (req, res) => {
-  let urlPath = decodeURIComponent((req.url || "/").split("?")[0]);
+  let urlPath;
+  try {
+    urlPath = decodeURIComponent((req.url || "/").split("?")[0]);
+  } catch {
+    res.statusCode = 400;
+    return res.end("Bad request");
+  }
   if (urlPath.endsWith("/")) urlPath += "index.html";

-  const filePath = join(PUBLIC_DIR, urlPath);
+  const filePath = resolve(PUBLIC_DIR, `.${urlPath}`);
   // Prevent path traversal outside public/.
-  if (!filePath.startsWith(PUBLIC_DIR)) {
+  if (!(filePath === PUBLIC_DIR || filePath.startsWith(PUBLIC_DIR + sep))) {
     res.statusCode = 403;
     return res.end("Forbidden");
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let urlPath = decodeURIComponent((req.url || "/").split("?")[0]);
if (urlPath.endsWith("/")) urlPath += "index.html";
const filePath = join(PUBLIC_DIR, urlPath);
// Prevent path traversal outside public/.
if (!filePath.startsWith(PUBLIC_DIR)) {
res.statusCode = 403;
return res.end("Forbidden");
}
import { join, extname, dirname, resolve, sep } from "node:path";
Suggested change
let urlPath = decodeURIComponent((req.url || "/").split("?")[0]);
if (urlPath.endsWith("/")) urlPath += "index.html";
const filePath = join(PUBLIC_DIR, urlPath);
// Prevent path traversal outside public/.
if (!filePath.startsWith(PUBLIC_DIR)) {
res.statusCode = 403;
return res.end("Forbidden");
}
let urlPath;
try {
urlPath = decodeURIComponent((req.url || "/").split("?")[0]);
} catch {
res.statusCode = 400;
return res.end("Bad request");
}
if (urlPath.endsWith("/")) urlPath += "index.html";
const filePath = resolve(PUBLIC_DIR, `.${urlPath}`);
// Prevent path traversal outside public/.
if (!(filePath === PUBLIC_DIR || filePath.startsWith(PUBLIC_DIR + sep))) {
res.statusCode = 403;
return res.end("Forbidden");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/serve.mjs` around lines 27 - 35, The path validation has two
security/stability issues: the `filePath.startsWith(PUBLIC_DIR)` check is
vulnerable to prefix-confusion attacks (e.g., `/publicity` bypasses the guard)
and `decodeURIComponent` throws unhandled exceptions on malformed
percent-encoding. To fix this, wrap the `decodeURIComponent` call in a try-catch
block to respond with a 400 Bad Request on invalid input, and harden the path
check by using `resolve()` to normalize both `filePath` and `PUBLIC_DIR` before
comparing, ensuring the resolved filePath actually starts with the normalized
PUBLIC_DIR directory path rather than just matching the string prefix.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cfce959f10

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/generate.mjs
const { data, content } = matter(raw);
const fileStat = await stat(full);
const noteDate = data.date ? new Date(data.date) : fileStat.mtime;
if (noteDate < cutoff) continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound notes to the requested generation date

When --date is used for a backfill or when a note is accidentally future-dated, this check only drops notes older than the lower cutoff; anything after the target date still gets included. For example, generating --date 2026-06-10 with a memory/2026-06-12*.md note will publish content from after the requested day, so add an upper-bound check against the target date.

Useful? React with 👍 / 👎.

Comment thread scripts/generate.mjs
Comment on lines +59 to +61
const files = (await readdir(MEMORY_DIR)).filter(
(f) => extname(f) === ".md" && !f.startsWith(".") && f !== "README.md"
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude sample memories from generation

Because this collector accepts every non-hidden memory/*.md except README.md, the committed memory/2026-06-12-example.md is treated as a real note. On scheduled/manual runs within seven days of 2026-06-12, a repo with no user notes and no API keys will still open a dry-run PR based on the sample text rather than doing nothing, so move examples outside memory/ or explicitly ignore example files.

Useful? React with 👍 / 👎.

@xiami303

Copy link
Copy Markdown
Owner Author

@codifyai 来测试

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant