Skip to content

[blog-draft] Global Technology Audit Guide: The Evidence Gap MENA Audit Firms Face - #77

Merged
Smetools merged 2 commits into
mainfrom
hassan/blogger-20260618
Jun 18, 2026
Merged

[blog-draft] Global Technology Audit Guide: The Evidence Gap MENA Audit Firms Face#77
Smetools merged 2 commits into
mainfrom
hassan/blogger-20260618

Conversation

@Smetools

@Smetools Smetools commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Draft metadata

  • Persona: mena-audit-firms
  • Mode: thesis (case-study: Demo/MENA Audit Firms has 0 sources → fail; comparison: read-ai already in shipped posts → fail)
  • Keyword: global technology audit guide (vol 140, UAE, LOW competition)
  • Word count: 1,599
  • Slug: global-technology-audit-guide-evidence-gap-mena

Validation gates

Gate Result
Word count (1,300–1,600) ✅ 1,599
Keyword in title
Keyword in H2
Keyword in body (≥3) ✅ ×9
FAQ count (exactly 5)
Banned words ✅ none
Slug unique vs shipped + drafts

State updates (gitignored, applied to shared checkout)

  • state.json cursor: 1 → 2 (next persona: mena-agencies)
  • burn-state.json total_posts: 3 → 4, posts_this_week: 3 → 4

Angle

ISACA's Global Technology Audit Guide specifies what evidence to collect but not how to document that the auditor confirmed AI-extracted claims from interview recordings. For MENA audit firms under PDPL Article 36 enforcement pressure (48 SDAIA decisions in year 1), Knowcap's verified-fact model provides the missing evidence chain — each confirmed claim traceable to a named reviewer and timestamped source. No-Confirm-All constraint maps directly onto audit working-paper discipline.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation

    • Added a new Global Technology Audit Guide draft for MENA audit firms focused on closing evidence gaps.
    • Explains how interview-derived claims should be verified with human-confirmed, timestamped source support to strengthen working-paper defensibility.
    • Includes an engagement walkthrough and FAQ covering GTAG alignment, Arabic-language interviews, and documentation expectations for AI-assisted outputs.
  • Automation

    • Introduced a streamlined content workflow that automatically publishes merged blog drafts and maintains the published archive.

…ap for MENA audit firms (#78)

Persona: mena-audit-firms | Mode: thesis | Keyword: global technology audit guide (vol 140, UAE, LOW)
Word count: 1,599 | FAQ: 5 | All gates pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
knowcap-landing Ready Ready Preview, Comment Jun 18, 2026 9:09am

Request Review

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds automated blog draft publishing infrastructure and introduces a MENA GTAG evidence-gap draft as the first published example. The PR includes a Node.js script that converts draft Markdown into published blog posts by transforming YAML frontmatter and content, a GitHub Actions workflow that auto-publishes on merged [blog-draft] PRs, and a new MENA GTAG evidence-gap draft document.

Changes

Automated Blog Draft Publishing

Layer / File(s) Summary
Draft Publishing Script - Parsing and Data Transformation
scripts/publish-draft.mjs
Implements parseFrontmatter() to extract and parse YAML metadata blocks, extractDescription() to derive meta descriptions from body content, and slugToTags() and readMinutes() to generate normalized tags and read-time estimates. Includes buildPublishedFrontmatter() to construct final frontmatter with derived/normalized fields (slug, date, description, tags, geo_audiences, persona, read_minutes) and serializeFrontmatter() to output YAML-like text with proper escaping and quoting.
Draft Publishing Script - CLI Execution and File Operations
scripts/publish-draft.mjs
Implements main entry point: validates CLI arguments and draft existence, parses frontmatter and body content, enforces slug presence, builds final published frontmatter, writes output to app/content/blog/<slug>.md (refusing if target exists), deletes the original draft, and logs the results.
Draft Publishing Workflow - Automated Publishing Trigger
.github/workflows/publish-blog-draft.yml
Defines a GitHub Actions workflow triggered on closed, merged PRs with [blog-draft] title on main. Workflow checks out main, installs Node.js 20, detects changed Markdown files under docs/content-pipeline/drafts/, publishes each found draft by invoking publish-draft.mjs, stages output and draft directories, and commits/pushes changes with skip-ci marker.
MENA GTAG Evidence-Gap Draft Document
docs/content-pipeline/drafts/global-technology-audit-guide-evidence-gap-mena.md
YAML frontmatter defines title, slug, persona, keywords, geo audience, word count, draft date, and status. Main narrative describes the GTAG interview-evidence gap and specifies the verified-facts workflow requiring individual auditor confirmation of each claim against timestamped recordings. FAQ addresses ISACA GTAG methodology alignment, Arabic-language interview handling, human-confirmed evidence definition for working papers, PDPL Article 36 suitability, and coexistence with existing audit platforms. Closing section frames the verified-facts layer as addressing the AI evidence gap for MENA firms.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 A workflow now hops when PRs come merge,
Draft to published post with a script-powered surge.
MENA auditors gain verified claims in their hands,
Each fact timestamped across GTAG lands,
From [blog-draft] trigger to evidence-full strands! 📚

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly and specifically describes the main change: introducing a blog draft about evidence documentation gaps for MENA audit firms using ISACA's Global Technology Audit Guide, which aligns perfectly with the core content addition.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 hassan/blogger-20260618

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

- .github/workflows/publish-blog-draft.yml — triggers on any [blog-draft] PR merge to main; finds draft files from PR diff, calls publish-draft.mjs, commits shipped posts
- scripts/publish-draft.mjs — transforms blogger-routine frontmatter to blog frontmatter (drops internal keys, computes readMinutes, extracts description, derives tags + geo_audiences), moves draft → app/content/blog/, deletes draft
- draft frontmatter: added description, tags, author, lang, dir

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@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: 3

🤖 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/publish-blog-draft.yml:
- Around line 38-50: The here-string at the end of the while loop directly
interpolates the GitHub Actions template variable without proper quoting, which
creates a command injection vulnerability. To fix this, add double quotes around
the ${{ steps.find_drafts.outputs.drafts }} variable in the here-string syntax
(done <<< "${{ ... }}") to ensure the output is treated as a literal string and
any special characters in filenames are not interpreted by the shell.
- Around line 18-25: The workflow uses mutable action version tags (v4) which
increases security risk and relies on persisted credentials from the checkout
action. Replace the `actions/checkout@v4` reference with a pinned commit SHA,
add `persist-credentials: false` to disable credential persistence in that step,
and replace `actions/setup-node@v4` with a pinned commit SHA as well.
Additionally, identify any git push commands in the workflow and update them to
explicitly pass the GITHUB_TOKEN environment variable instead of relying on
auto-persisted credentials from checkout.

In `@scripts/publish-draft.mjs`:
- Around line 165-174: The meta.slug is being used directly as a path component
in the outPath construction without validation, creating a path traversal
vulnerability where a slug containing sequences like ../ could escape the
BLOG_DIR directory. Add validation after the existing slug existence check to
ensure meta.slug contains only safe characters and does not include path
traversal patterns. The validation should reject slugs containing forward
slashes, backslashes, or dot sequences before passing it to path.join(BLOG_DIR,
`${meta.slug}.md`) to construct the outPath safely.
🪄 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: f15fd767-5e21-4a16-9881-726d4b1a76e4

📥 Commits

Reviewing files that changed from the base of the PR and between 32401d3 and 1715ef0.

📒 Files selected for processing (3)
  • .github/workflows/publish-blog-draft.yml
  • docs/content-pipeline/drafts/global-technology-audit-guide-evidence-gap-mena.md
  • scripts/publish-draft.mjs
✅ Files skipped from review due to trivial changes (1)
  • docs/content-pipeline/drafts/global-technology-audit-guide-evidence-gap-mena.md

Comment on lines +18 to +25
- uses: actions/checkout@v4
with:
ref: main
token: ${{ secrets.GITHUB_TOKEN }}

- uses: actions/setup-node@v4
with:
node-version: '20'

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:

find . -name "publish-blog-draft.yml" -type f

Repository: Knowcap-V2/knowcap-website

Length of output: 113


🏁 Script executed:

cat -n .github/workflows/publish-blog-draft.yml

Repository: Knowcap-V2/knowcap-website

Length of output: 2533


Pin third-party actions to specific commit SHAs and disable credential persistence.

This workflow has contents: write permission and performs git push operations. Using mutable action tags (@v4) and persisted credentials from checkout increases the compromise blast radius unnecessarily.

  • Replace actions/checkout@v4 with a pinned commit SHA and add persist-credentials: false
  • Replace actions/setup-node@v4 with a pinned commit SHA
  • Pass GITHUB_TOKEN explicitly to the git push command instead of relying on persisted credentials:
    git push "https://x-access-token:${GITHUB_TOKEN}`@github.com/`${GITHUB_REPOSITORY}.git" HEAD:main
🧰 Tools
🪛 zizmor (1.25.2)

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

(artipacked)


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

(unpinned-uses)


[error] 23-23: 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/publish-blog-draft.yml around lines 18 - 25, The workflow
uses mutable action version tags (v4) which increases security risk and relies
on persisted credentials from the checkout action. Replace the
`actions/checkout@v4` reference with a pinned commit SHA, add
`persist-credentials: false` to disable credential persistence in that step, and
replace `actions/setup-node@v4` with a pinned commit SHA as well. Additionally,
identify any git push commands in the workflow and update them to explicitly
pass the GITHUB_TOKEN environment variable instead of relying on auto-persisted
credentials from checkout.

Source: Linters/SAST tools

Comment on lines +38 to +50
- name: Publish each draft
if: steps.find_drafts.outputs.drafts != ''
run: |
while IFS= read -r draft; do
[ -z "$draft" ] && continue
if [ -f "$draft" ]; then
echo "Publishing: $draft"
node scripts/publish-draft.mjs "$draft"
else
echo "Draft already removed from main (possibly already published): $draft"
fi
done <<< "${{ steps.find_drafts.outputs.drafts }}"

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 | 🔴 Critical | ⚡ Quick win

Avoid direct template interpolation in shell here-string (command injection risk).

Using ${{ ... }} directly in done <<< "..." allows shell interpretation of attacker-controlled filename content.

🛡️ Suggested fix
       - name: Publish each draft
         if: steps.find_drafts.outputs.drafts != ''
+        env:
+          DRAFTS: ${{ steps.find_drafts.outputs.drafts }}
         run: |
           while IFS= read -r draft; do
             [ -z "$draft" ] && continue
             if [ -f "$draft" ]; then
               echo "Publishing: $draft"
               node scripts/publish-draft.mjs "$draft"
             else
               echo "Draft already removed from main (possibly already published): $draft"
             fi
-          done <<< "${{ steps.find_drafts.outputs.drafts }}"
+          done <<< "$DRAFTS"
📝 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
- name: Publish each draft
if: steps.find_drafts.outputs.drafts != ''
run: |
while IFS= read -r draft; do
[ -z "$draft" ] && continue
if [ -f "$draft" ]; then
echo "Publishing: $draft"
node scripts/publish-draft.mjs "$draft"
else
echo "Draft already removed from main (possibly already published): $draft"
fi
done <<< "${{ steps.find_drafts.outputs.drafts }}"
- name: Publish each draft
if: steps.find_drafts.outputs.drafts != ''
env:
DRAFTS: ${{ steps.find_drafts.outputs.drafts }}
run: |
while IFS= read -r draft; do
[ -z "$draft" ] && continue
if [ -f "$draft" ]; then
echo "Publishing: $draft"
node scripts/publish-draft.mjs "$draft"
else
echo "Draft already removed from main (possibly already published): $draft"
fi
done <<< "$DRAFTS"
🧰 Tools
🪛 zizmor (1.25.2)

[info] 49-49: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 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/publish-blog-draft.yml around lines 38 - 50, The
here-string at the end of the while loop directly interpolates the GitHub
Actions template variable without proper quoting, which creates a command
injection vulnerability. To fix this, add double quotes around the ${{
steps.find_drafts.outputs.drafts }} variable in the here-string syntax (done <<<
"${{ ... }}") to ensure the output is treated as a literal string and any
special characters in filenames are not interpreted by the shell.

Source: Linters/SAST tools

Comment thread scripts/publish-draft.mjs
Comment on lines +165 to +174
if (!meta.slug) {
console.error('Draft missing slug in frontmatter')
process.exit(1)
}

const pubMeta = buildPublishedFrontmatter(meta, body)
const published = serializeFrontmatter(pubMeta) + '\n' + body

const outPath = path.join(BLOG_DIR, `${meta.slug}.md`)
if (fs.existsSync(outPath)) {

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 | 🔴 Critical | ⚡ Quick win

Enforce a strict slug format before building the publish path.

meta.slug is currently trusted as a path component. A slug containing ../ can escape app/content/blog and overwrite arbitrary repo files during auto-publish.

🔒 Suggested fix
+const SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
+
 if (!meta.slug) {
   console.error('Draft missing slug in frontmatter')
   process.exit(1)
 }
+if (!SLUG_RE.test(meta.slug)) {
+  console.error(`Invalid slug: ${meta.slug}`)
+  process.exit(1)
+}
 
 const pubMeta = buildPublishedFrontmatter(meta, body)
 const published = serializeFrontmatter(pubMeta) + '\n' + body
 
-const outPath = path.join(BLOG_DIR, `${meta.slug}.md`)
+const outPath = path.resolve(BLOG_DIR, `${meta.slug}.md`)
+if (!outPath.startsWith(path.resolve(BLOG_DIR) + path.sep)) {
+  console.error(`Refusing to write outside blog directory: ${outPath}`)
+  process.exit(1)
+}
 if (fs.existsSync(outPath)) {
   console.error(`Blog post already exists: ${outPath}`)
   process.exit(1)
 }
📝 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
if (!meta.slug) {
console.error('Draft missing slug in frontmatter')
process.exit(1)
}
const pubMeta = buildPublishedFrontmatter(meta, body)
const published = serializeFrontmatter(pubMeta) + '\n' + body
const outPath = path.join(BLOG_DIR, `${meta.slug}.md`)
if (fs.existsSync(outPath)) {
const SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
if (!meta.slug) {
console.error('Draft missing slug in frontmatter')
process.exit(1)
}
if (!SLUG_RE.test(meta.slug)) {
console.error(`Invalid slug: ${meta.slug}`)
process.exit(1)
}
const pubMeta = buildPublishedFrontmatter(meta, body)
const published = serializeFrontmatter(pubMeta) + '\n' + body
const outPath = path.resolve(BLOG_DIR, `${meta.slug}.md`)
if (!outPath.startsWith(path.resolve(BLOG_DIR) + path.sep)) {
console.error(`Refusing to write outside blog directory: ${outPath}`)
process.exit(1)
}
if (fs.existsSync(outPath)) {
🤖 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/publish-draft.mjs` around lines 165 - 174, The meta.slug is being
used directly as a path component in the outPath construction without
validation, creating a path traversal vulnerability where a slug containing
sequences like ../ could escape the BLOG_DIR directory. Add validation after the
existing slug existence check to ensure meta.slug contains only safe characters
and does not include path traversal patterns. The validation should reject slugs
containing forward slashes, backslashes, or dot sequences before passing it to
path.join(BLOG_DIR, `${meta.slug}.md`) to construct the outPath safely.

@Smetools
Smetools merged commit c780336 into main Jun 18, 2026
3 checks passed
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