io.github.GarAlex/promoshot
version 0.3.4
- scanned 2026-10-16
- source github.com/GarAlex/promoshot
- commit fc0be0f5b5f9
- transport stdio
- files read 13
- methodology 0.2.0
- ruleset 0.1.1
73
100measured on 65% of this axis
not assessed
7 vulnerabilities were found and none could be placed in `03` §6's table — no CVSS score, or an ecosystem whose manifest we cannot parse for direct-vs-transitive
77measured on 75% of this axis
36
The evidence
Every deduction below names the artefact that produced it. Nothing on this page was executed: the source was read, never run, and no running server was contacted.
Code Safety
- demos/c12-through-the-surfaces/reference.py:194mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
if __name__ == '__main__': doc = build() json.dump(doc, open(os.path.join(HERE, 'reference.json'), 'w'), indent=1) pkg = os.path.join(HERE, 'runs', 'reference', 'Through the surfaces.promo') os.makedirs(os.path.join(pkg, 'Resources'), exist_ok=True) - demos/c12-through-the-surfaces/reference.py:197mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
pkg = os.path.join(HERE, 'runs', 'reference', 'Through the surfaces.promo') os.makedirs(os.path.join(pkg, 'Resources'), exist_ok=True) json.dump(doc, open(os.path.join(pkg, 'metadata.json'), 'w'), indent=1) v = subprocess.run([CLI, 'validate', pkg], capture_output=True, text=True); print(v.stdout.strip()[:600] or v.stderr[-400:]) work = os.path.dirname(pkg) - demos/c13-carousel-of-films/reference.py:150mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
if __name__ == '__main__': doc = build() json.dump(doc, open(os.path.join(HERE, 'reference.json'), 'w'), indent=1) pkg = os.path.join(HERE, 'runs', 'reference', 'A carousel of films.promo') os.makedirs(os.path.join(pkg, 'Resources'), exist_ok=True) - demos/c13-carousel-of-films/reference.py:153mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
pkg = os.path.join(HERE, 'runs', 'reference', 'A carousel of films.promo') os.makedirs(os.path.join(pkg, 'Resources'), exist_ok=True) json.dump(doc, open(os.path.join(pkg, 'metadata.json'), 'w'), indent=1) v = subprocess.run([CLI, 'validate', pkg], capture_output=True, text=True); print(v.stdout.strip()[:1200] or v.stderr[-400:]) work = os.path.dirname(pkg) - demos/c9-infinite-screens/build.py:370mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
pkg = os.path.join(work, film['name'] + '.promo') meta = build(film); scenes = meta.pop('_scenes'); materialize(meta, pkg) json.dump(meta, open(os.path.join(HERE, film['reference']), 'w'), indent=1) print('scenes:', [(n, round(t, 2), round(d, 2)) for n, t, d in scenes], 'total', round(meta['videoDuration'], 2)) v = subprocess.run([CLI, 'validate', pkg], capture_output=True, text=True); print(v.stdout.strip()[:800]) - demos/creative.py:13mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
import json, os, shutil HERE = os.path.dirname(os.path.abspath(__file__)) TASK = open(os.path.join(HERE, 'task.md')).read() SETS = [ - demos/creative.py:81mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
prompt += "\nFiles in `resources/`: " + ", ".join(media) + ".\n" prompt += TASK open(os.path.join(folder, 'prompt.md'), 'w').write(prompt) json.dump({"kind": "creative", "template": f"{s['id'].upper()} {s['title']}", "media": media, "duration": s['duration'], "must_use": s['must_use']}, - demos/creative.py:84mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
json.dump({"kind": "creative", "template": f"{s['id'].upper()} {s['title']}", "media": media, "duration": s['duration'], "must_use": s['must_use']}, open(os.path.join(folder, 'rubric.json'), 'w'), indent=1) print(f" {os.path.basename(folder)}: {len(media)} files") - demos/import_templates.py:16mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
SHARED_OVER = 1_000_000 PROMPTS = json.load(open(os.path.join(HERE, 'prompts.json'))) TASK = open(os.path.join(HERE, 'task.md')).read() - demos/import_templates.py:17mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
PROMPTS = json.load(open(os.path.join(HERE, 'prompts.json'))) TASK = open(os.path.join(HERE, 'task.md')).read() def slug(name): - demos/import_templates.py:31mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
if nn not in PROMPTS: continue meta = json.load(open(os.path.join(lib, d, 'metadata.json'))) name = d[:-6] folder = os.path.join(HERE, f"{nn}-{slug(name[3:])}") - demos/import_templates.py:51mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
shutil.copy2(src, os.path.join(folder, 'resources', f)) media.append(f) json.dump(meta, open(os.path.join(folder, 'reference.json'), 'w'), indent=1) rubric = rubric_from(meta, name, media) prompt = PROMPTS[nn].strip() + "\n" - demos/import_templates.py:59mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
prompt += "\nText to use, in order:\n" + "".join(f"- {t}\n" for t in rubric['phrases']) prompt += TASK open(os.path.join(folder, 'prompt.md'), 'w').write(prompt) json.dump(rubric, open(os.path.join(folder, 'rubric.json'), 'w'), indent=1) print(f" {os.path.basename(folder)}: {len(media)} files, {len(rubric['phrases'])} lines") - demos/import_templates.py:60mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
prompt += TASK open(os.path.join(folder, 'prompt.md'), 'w').write(prompt) json.dump(rubric, open(os.path.join(folder, 'rubric.json'), 'w'), indent=1) print(f" {os.path.basename(folder)}: {len(media)} files, {len(rubric['phrases'])} lines") - demos/publish.py:64mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
stats = {} try: j = json.load(open(os.path.join(run, 'agent.json'))) except Exception: return stats - demos/publish.py:150mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
return f"| {pic} | [{template}](docs/demo/demo{ident(name)}.md) | {blurb} | {films} |" BLURBS = json.load(open(os.path.join(HERE, 'blurbs.json'))) if os.path.exists(os.path.join(HERE, 'blurbs.json')) else {} def publish_showcase(name, demo, rubric, srows): - demos/publish.py:160mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
shutil.rmtree(out, ignore_errors=True) os.makedirs(out) prompt = open(os.path.join(demo, 'prompt.md')).read().strip() open(os.path.join(out, 'prompt.md'), 'w').write(prompt + "\n") blurb = rubric.get('blurb') or prompt.split('.')[0].strip() + '.' - demos/publish.py:161mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
os.makedirs(out) prompt = open(os.path.join(demo, 'prompt.md')).read().strip() open(os.path.join(out, 'prompt.md'), 'w').write(prompt + "\n") blurb = rubric.get('blurb') or prompt.split('.')[0].strip() + '.' result = {"films": []} - demos/publish.py:222mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
pg.append("") pg += ["---", "", "[← all demos](../../demo.md) · [how the suite works](../../demos/README.md)", ""] open(os.path.join(ASSETS, f"demo{ident(name)}.md"), 'w').write("\n".join(pg)) srows.append(showcase_row(name, rubric['template'], blurb, result)) return {"id": ident(name), "slug": name, "title": title_of(rubric), "blurb": blurb, "kind": "showcase", - demos/publish.py:234mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
if only: try: previous = {m['slug']: m for m in json.load(open(os.path.join(ASSETS, 'demo.json')))} except Exception: previous = {} - demos/publish.py:247mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
if not (os.path.isdir(demo) and (ident(name).isdigit() or creative) and os.path.exists(os.path.join(demo, 'rubric.json'))): continue rubric = json.load(open(os.path.join(demo, 'rubric.json'))) if only and name not in only and name in previous: # Untouched: the entry, the page and the assets stand as published. - demos/publish.py:260mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
manifest.append(publish_showcase(name, demo, rubric, srows)) continue prompt = open(os.path.join(demo, 'prompt.md')).read().strip() user_prompt = prompt.split('\nUse the PromoShot skill')[0].strip() out = os.path.join(ASSETS, name) - demos/publish.py:284mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
entry["poster"] = f"docs/demo/{name}/resources/{os.path.basename(p)}" resources.append(entry) open(os.path.join(out, 'prompt.md'), 'w').write(user_prompt + "\n") run = latest_run(demo) result = None - demos/publish.py:288mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
result = None if run: score = json.load(open(os.path.join(run, 'score.json'))) summary_path = os.path.join(run, 'summary.txt') summary = open(summary_path).read().strip() if os.path.exists(summary_path) else '' - demos/publish.py:295mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
"run": run_stats(run), "used": score.get('used'), "captions": score.get('captions')} try: notes = json.load(open(os.path.join(run, 'agent.json'))).get('result') or '' result["notes"] = notes.strip()[:1800] except Exception: - demos/publish.py:406mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
pg += ["## What the agent made", "", "*Not run yet.*", ""] pg += ["---", "", "[← all demos](../../demo.md) · [how the suite works](../../demos/README.md)", ""] open(os.path.join(ASSETS, f"demo{ident(name)}.md"), 'w').write("\n".join(pg)) # --- the index row --- - demos/publish.py:469mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
if srows: index += showcase_head + "\n".join(srows) + "\n" open(os.path.join(CORE, 'demo.md'), 'w').write(index) json.dump(manifest, open(os.path.join(ASSETS, 'demo.json'), 'w'), indent=1) done = sum(1 for m in manifest if m['result']) - demos/publish.py:470mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
index += showcase_head + "\n".join(srows) + "\n" open(os.path.join(CORE, 'demo.md'), 'w').write(index) json.dump(manifest, open(os.path.join(ASSETS, 'demo.json'), 'w'), indent=1) done = sum(1 for m in manifest if m['result']) print(f"demo.md: {len(manifest)} demos, {done} with results; pages and assets in docs/demo") - demos/scenario_check.py:57mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
def main(): demo, run = sys.argv[1], sys.argv[2] expect = json.load(open(os.path.join(demo, "scenario.json"))).get("expect", {}) ws = os.path.join(run, "ws") calls, answer = transcript(run) - demos/score.py:83mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
def score(demo, project): rubric = json.load(open(os.path.join(demo, 'rubric.json'))) meta = json.load(open(os.path.join(project, 'metadata.json'))) if rubric.get('kind') == 'creative': - demos/score.py:84mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
def score(demo, project): rubric = json.load(open(os.path.join(demo, 'rubric.json'))) meta = json.load(open(os.path.join(project, 'metadata.json'))) if rubric.get('kind') == 'creative': return score_creative(rubric, meta, project) - promo-cli/examples/chapters.py:474mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
uuidify(meta) meta = merge_into_existing(out_dir, meta) with open(os.path.join(out_dir, "metadata.json"), "w") as handle: json.dump(meta, handle, indent=2) - promo-cli/examples/marketing.py:206mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
for src, filename in assets: shutil.copyfile(src, os.path.join(out_dir, "Resources", filename)) with open(os.path.join(out_dir, "metadata.json"), "w") as f: json.dump(meta, f, indent=2) - promo-cli/examples/reel.py:212mcp-python-path-join-unvalidated (medium/low)
A filesystem path is assembled from a value that is not a literal and opened without confinement. os.path.join discards the base entirely when the second component is absolute, and neither join nor `/` resolves `..`.
cursor = start + usable - FADE with open(os.path.join(out_dir, "metadata.json"), "w") as f: json.dump(meta, f, indent=2)
Auth Posture
- authentication_model
stdio transport: the client launches the server as a subprocess, so the OS process boundary is the auth boundary — `03` §4 does not penalise absent auth here
- transport_security
declared transport: stdio — launched as a subprocess by the client, so the security boundary is the client process, not a network port (`03` §4)
- secret_handling
detect-secrets matched 12 credential-shaped string(s), every one from a keyword or entropy heuristic (Hex High Entropy String). Those flag placeholders, variable names and content hashes as readily as credentials, so they do not establish the committed credential `03` §4's 0 asserts, and this sub-check is not scored; Hex High Entropy String at demos/07-narrated-tour/reference.json:76; Hex High Entropy String at demos/07-narrated-tour/reference.json:92; Hex High Entropy String at demos/07-narrated-tour/reference.json:108; Hex High Entropy String at demos/07-narrated-tour/reference.json:124; Hex High Entropy String at demos/07-narrated-tour/reference.json:140; Hex High Entropy String at demos/21-chapters-levels/reference.json:80; Hex High Entropy String at demos/21-chapters-levels/reference.json:107; Hex High Entropy String at demos/21-chapters-levels/reference.json:134; Hex High Entropy String at demos/21-chapters-levels/reference.json:161; Hex High Entropy String at demos/21-chapters-levels/reference.json:188; …and 2 more matches
- authorization_granularity
no tool registration was recognised anywhere in the source. An MCP server registers tools by definition, so this is a gap in our detection rather than a server with none, and `03` §4's threshold cannot be applied to it.
Dependency Health
- Cargo.lockrustls 0.23.43 — RUSTSEC-2026-0285
direct or transitive unknown — no parseable manifest for this ecosystem, CVSS 5.3, fixed in 0.23.45
https://osv.dev/vulnerability/RUSTSEC-2026-0285 - Cargo.lockrustls 0.23.43 — RUSTSEC-2026-0346
direct or transitive unknown — no parseable manifest for this ecosystem, CVSS 4.0, fixed in 0.23.46
https://osv.dev/vulnerability/RUSTSEC-2026-0346 - Cargo.lockrustls 0.23.43 — RUSTSEC-2026-0348
direct or transitive unknown — no parseable manifest for this ecosystem, CVSS 3.7, fixed in 0.23.46
https://osv.dev/vulnerability/RUSTSEC-2026-0348 - Cargo.lockrustls 0.23.43 — RUSTSEC-2026-0349
direct or transitive unknown — no parseable manifest for this ecosystem, CVSS 3.7, fixed in 0.23.46
https://osv.dev/vulnerability/RUSTSEC-2026-0349 - Cargo.lockpaste 1.0.15 — RUSTSEC-2024-0436
direct or transitive unknown — no parseable manifest for this ecosystem, no CVSS published
https://osv.dev/vulnerability/RUSTSEC-2024-0436 - Cargo.lockttf-parser 0.21.1 — RUSTSEC-2026-0192
direct or transitive unknown — no parseable manifest for this ecosystem, no CVSS published
https://osv.dev/vulnerability/RUSTSEC-2026-0192 - Cargo.lockttf-parser 0.25.1 — RUSTSEC-2026-0192
direct or transitive unknown — no parseable manifest for this ecosystem, no CVSS published
https://osv.dev/vulnerability/RUSTSEC-2026-0192
Maintenance
- recency
last commit 14 day(s) before 2026-10-16
- issue_responsiveness
lifetime median (1 issue(s) in the last 6 months, under `03` §5's floor of 3): 42.1 day(s) to first maintainer comment
- release_cadence
released 14 day(s) ago (by releases), inside `03` §5's 30-day window
- bus_factor
this repository's 12-month history is longer than the bounded walk `04` §7 performs, and the authors found so far span more than one of `03` §5's bands. A limit of ours, not a finding about the server.
- repository_signals
`03` §5 scores this by quartile WITHIN the server's category, which needs a distribution across every scanned server and so cannot be computed while scanning one. It is a post-pass over the crawl and that pass does not exist yet.
Transparency
- license
LICENSE present with no SPDX-License-Identifier tag; identified as 'Apache-2.0' from its text
- readme_quality
README.md: configuration documented (+25), exposed tools or resources enumerated (+30), states what the server does in the first 200 words (+20)
- declared_scopes
neither the README nor the MCP manifest documents what the server accesses or what side effects it has
- changelog
no CHANGELOG in the fetched source. `03` §7's partial credit for tagged releases carrying release notes is not applied here — that needs the forge API, which this check does not call
- security_contact
no SECURITY.md and no disclosure contact in the README, so a finder has no route to report a vulnerability privately
Contest a finding
Every number here comes from a rule reading published source, and rules get things wrong. If something on this page is incorrect — or correct but mitigated by a check the rule cannot see — open an issue and we answer it in writing. A finding we got wrong is removed and the rule is fixed; one that stands is answered on the record.
Anyone can file: the maintainer, a researcher who disagrees with the classification, or a user who tested the server and could not reproduce it. Name this server and the rule or advisory ID shown beside the finding above. We aim to reply within five business days and to resolve within ten.