Grades are a hygiene check, not a security guarantee.

Claude Code, Skill

Pptedit

创建、修改、调试和验收 H5、HTML 单页、落地页与网页 Deck。当任务需要修改真实网页源码并在浏览器中验证时使用;不适用于没有 HTML 运行时的原生演示文件。

Compatibility: Not stated.

Grade C

One Medium finding. Read the report before installing.

Graded by PlugVetRubric v2

Not a guarantee. A hygiene check of the source at the commit shown, not a security audit.

OBFUSCATION_MINIFIED_PAYLOAD

Why this grade

  • OBFUSCATION_MINIFIED_PAYLOAD Medium

    Contains a very long line with almost no spaces in a script or style file.

    assets/layout-reference.js, line 71 function acceptFile(file){if(busy||!file)return;figmaSource=null;updateMode();const version=++importVersion;if(activeImport){activeImport.abort();activeImport=null;}loadedUrl='';q(

Any High, or two or more Medium findings, give D. One Medium gives C. Low findings or a security Note give B. Each code names a finding behind the letter.

Grades are a hygiene check of the source at the commit shown, on the date checked, not a security audit or a guarantee. The free directory grades are automated. Before release, a PlugVet engineer reviews every paid report and any grade we hold back, including all F grades, against the source code and the evidence we quote.

What was checked

Plain notes from the hygiene checks.

  • Permissions

    Concern

    can delete files.

    PERMISSION_DELETE_FILES

    scripts/figma_mcp.py, line 35

    self.process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,

    scripts/figma_reference.py, line 33

    process=subprocess.run(['curl','--fail','--location','--silent','--show-error',

    scripts/local_codex.py, line 24

    result = subprocess.run([executable, '--version'], capture_output=True, text=True, timeout=10)

    Show more

    scripts/local_codex.py, line 62

    result = subprocess.run(command, input=prompt, text=True, encoding='utf-8', stdout=log, stderr=log,

    scripts/local_notes.py, line 33

    proc = subprocess.run([str(python), str(Path(__file__).with_name('generate-notes-local.py')),

    scripts/setup-local-codex.py, line 37

    login = subprocess.run([runtime['path'], 'login', 'status'], capture_output=True, text=True, timeout=15)

    scripts/setup-local-codex.py, line 60

    process = subprocess.Popen([sys.executable, str(Path(__file__).with_name('layout-bridge.py')),

    scripts/test-chapters-browser.mjs, line 61

    } finally { await browser?.close(); adapter.kill(); server.close(); await fs.rm(root, { recursive: true, force: true }); }

    scripts/test-figma-mcp.py, line 45

    patch('figma_mcp.subprocess.Popen',return_value=process),patch('figma_mcp.threading.Thread'),\

    scripts/test-order-sync-browser.mjs, line 55

    }finally{await browser?.close();adapter.kill();server.close();await fs.rm(root,{recursive:true,force:true});}

    scripts/test-overview-drag-browser.mjs, line 89

    }finally{await browser?.close();server.kill();staticServer.close();await fs.rm(root,{recursive:true,force:true});}

    scripts/test-page-grouping-browser.mjs, line 61

    }finally{await browser?.close();adapter.kill();server.close();await fs.rm(root,{recursive:true,force:true});}

    scripts/test-page-sorting-browser.mjs, line 103

    } finally {await browser?.close();server.kill();await fs.rm(root,{recursive:true,force:true});}

  • Install and update

    Pass

    No install-time script found, and dependencies are pinned or absent.

  • Network

    Concern

    can reach a host that is not written as a fixed address. can reach raw.githubusercontent.com. The description does not name that host.

    NETWORK_NO_FIXED_HOST NETWORK_UNNAMED_HOST

    assets/layout-reference.js, line 68

    path,options={})=>{const response=await fetch(config.url+path,{...options,headers:{'Co

    assets/notes-editor.js, line 23

    abort(),15000);try{const response=await fetch(config.url+path,{...options,signal:contr

    assets/pptedit-frame.js, line 30

    const response = await fetch('/state?page=' + page, { headers });

    Show more

    assets/pptedit-frame.js, line 96

    const response = await fetch('/delete-page', { method: 'POST', headers, body: JSON.stringify({ page: targetPage, previousOrder }) });

    assets/pptedit-frame.js, line 208

    const response = await fetch('/reorder', { method: 'POST', headers, body: JSON.stringify({ order, previousOrder, navigation }) });

    assets/pptedit-frame.js, line 336

    cached = { text: fetch(imagePath, { cache: 'no-cache' }).then(response => {

    assets/pptedit-frame.js, line 489

    const response = await fetch('/save', { method: 'POST', headers, body: JSON.stringify({ page, svg, revision: source.revision }) });

    assets/preview-edit-mode.js, line 114

    const response = await fetch(config.url + '/health', { signal: AbortSignal.timeout(4000) });

    scripts/test-chapters-browser.mjs, line 25

    for (let i = 0; i < 50; i++) { try { if ((await fetch(`http://127.0.0.1:${port}/health`)).ok) break; } catch {} await new Promise(r => setTimeout(r, 100)); }

    scripts/test-order-sync-browser.mjs, line 19

    for(let i=0;i<50;i++){try{if((await fetch(`http://127.0.0.1:${port}/health`)).ok)break;}catch{}await new Promise(r=>setTimeout(r,100));}

    scripts/test-overview-drag-browser.mjs, line 57

    for(let i=0;i<60;i++){try{if((await fetch(`http://127.0.0.1:${port}/health`)).ok)break;}catch{}await new Promise(r=>setTimeout(r,100));}

    scripts/test-page-delete-browser.mjs, line 23

    try { if ((await fetch(`http://127.0.0.1:${port}/health`)).ok) break; } catch {}

    scripts/test-page-grouping-browser.mjs, line 19

    for(let i=0;i<50;i++){try{if((await fetch(`http://127.0.0.1:${port}/health`)).ok)break;}catch{}await new Promise(r=>setTimeout(r,100));}

    scripts/test-page-grouping-browser.mjs, line 58

    const conflict=await fetch(`http://127.0.0.1:${port}/chapters`,{met

    scripts/test-page-sorting-browser.mjs, line 20

    for (let i=0;i<50;i++) {try {if ((await fetch(`http://127.0.0.1:${port}/health`)).ok) break;}catch{} await new Promise(r=>setTimeout(r,100));}

    scripts/test-svg-group-browser.mjs, line 22

    try { if ((await fetch(`http://127.0.0.1:${port}/health`)).ok) break; } catch {}

  • Secrets

    Concern

    reads a credential or environment file.

    SECRETS_PATH_DOTENV

    .gitignore, line 2

    ._*

    .gitignore, line 3

    .env

    .gitignore, line 5

    !.env.example

  • Agent instructions

    Pass

    No instruction to ignore earlier instructions or to hide a step.

  • Obfuscation

    Concern

    contains a minified payload.

    OBFUSCATION_MINIFIED_PAYLOAD

    assets/layout-reference.js, line 71

    function acceptFile(file){if(busy||!file)return;figmaSource=null;updateMode();const version=++importVersion;if(activeImport){activeImport.abort();activeImport=null;}loadedUrl='';q(

    assets/layout-reference.js, line 104

    async function loadHistory(){const targetPage=Number(page?.page),version=++historyVersion;historyList.textContent='正在读取当前页记录…';try{const result=await request('/history');if(version

    assets/h5-editor/editor.js, line 1

    /**

    Show more

    scripts/test-notes-editor.mjs, line 9

    const context={window:{PPT_NARRATION_HOST:{getSlides:()=>[slide],getIndex:()=>0,goTo:()=>rendered++},PPT_LAYOUT_REFERENCE:{url:'http://127.0.0.1:48765',token:'test'}},document:{get

    scripts/test-order-sync-browser.mjs, line 13

    await fs.writeFile(path.join(root,'index.html'),`<!doctype html><meta charset="utf-8"><link rel="stylesheet" href="/assets/preview-navigation.css"><link rel="stylesheet" href="/ass

    scripts/test-order-sync-browser.mjs, line 16

    const server=http.createServer(async(req,res)=>{try{const url=new URL(req.url,'http://local').pathname;const file=url.startsWith('/assets/')?path.join(process.cwd(),url):path.join(

    scripts/test-page-grouping-browser.mjs, line 13

    await fs.writeFile(path.join(root,'index.html'),`<!doctype html><meta charset="utf-8"><link rel="stylesheet" href="/assets/preview-navigation.css"><link rel="stylesheet" href="/ass

    scripts/test-page-grouping-browser.mjs, line 16

    const server=http.createServer(async(req,res)=>{try{const url=new URL(req.url,'http://local').pathname;const file=url.startsWith('/assets/')?path.join(process.cwd(),url):path.join(

  • Licence

    Pass

    A licence file is present (LICENSE).

  • Maintainer activity

    Pass

    The latest commit is within the last 365 days.

Note

Hygiene check of commit 115bb9c8280d. can delete files. can reach a host that is not written as a fixed address. can reach raw.githubusercontent.com. The description does not name that host. reads a credential or environment file. contains a minified payload.

Date and source

Date checked:

View source

github.com

Commit graded: 115bb9c8280d41339c1f79618d374cf7576adc66

This link goes to the original host. PlugVet does not host or check what you download from there, and installing it is your own decision and risk.

Read how this letter is chosen