T09 · Insecure Skill Coding Practices
- Location
scripts/peck_listener.py:184- Finding
Unauthenticated Path Traversal Enables Arbitrary JSON File Overwrite
- Content
View full analysis
Vulnerability Details
File Location:
scripts/peck_listener.py, lines 1–24, 184–202, 405–428, 1228–1229, and 1286
Vulnerability Type: Unauthenticated path traversal and arbitrary file overwrite
Risk Level: HighVulnerable Code
python """ AUTH: The backend identifies itself with X-SpaceDuck-Event: peck.received header. There is no inbound HMAC today ... """python def _fetch_shared_mds_for_peck(event, listener_cfg): refs = event.get('shared_mds') or [] if not refs: return None peck_id = event.get('peck_id') or f'peck_{int(time.time()*1000)}' files_dir = INBOX / f'{peck_id}.files' try: files_dir.mkdir(parents=True, exist_ok=True)python def do_POST(self): if self.path != '/peck': self.send_response(404); self.end_headers(); return n = int(self.headers.get('Content-Length') or 0) raw = self.rfile.read(n) if n else b'' try: event = json.loads(raw) except Exception: self._send_json(400, {'error': 'invalid_json'}); return evt_hdr = self.headers.get('X-SpaceDuck-Event', '?') peck_id = event.get('peck_id') or f'peck_{int(time.time()*1000)}' sender = event.get('sender_name') or event.get('sender_spaceduck_id') or '?' msg = (event.get('message') or '')[:140] # Persist to inbox before any side-effects. try: INBOX.mkdir(parents=True, exist_ok=True) inbox_path = INBOX / f'{peck_id}.json' inbox_path.write_text(json.dumps(event, indent=2)) _align_inbox_ownership(inbox_path)python p.add_argument('--host', default='0.0.0.0', help='HTTP server bind host (push mode only)')python srv = ThreadingHTTPServer((args.host, args.port), PeckHandler)Technical Analysis
The push-mode listener accepts arbitrary JSON requests at
/peckwithout cryptographic authentication. TheX-SpaceDuck-Eventheader is read only for display and is not validated as an aut ...[truncated 2708 chars]- Remediation
View remediation
Remediation Suggestions
-
Strictly validate
peck_idbefore any filesystem use. Permit only a constrained identifier format, for example:python import re if not isinstance(peck_id, str) or not re.fullmatch( r'[A-Za-z0-9_-]{1,128}', peck_id ): self._send_json(400, {'error': 'invalid_peck_id'}) return -
Enforce directory containment after canonicalization. Resolve the destination and verify that it remains beneath
INBOX:python inbox_root = INBOX.resolve() destination = (inbox_root / f'{peck_id}.json').resolve() if destination.parent != inbox_root: self._send_json(400, {'error': 'invalid_peck_id'}) returnApply equivalent containment checks to
files_dirand every attachment destination. -
Prefer locally generated storage names. Store untrusted protocol identifiers inside file contents or map them to a local random identifier instead of using them as filenames.
-
Authenticate inbound webhooks. Require an HMAC signature covering the timestamp, nonce, path, and raw body. Enforce a short timestamp window and nonce replay protection before parsing or persisting the event.
-
Use a safer network default. Bind push mode to
127.0.0.1by default and require an explicit option to expose it externally. -
Add regression tests covering
../config, absolute paths, nested separators, encoded separators, platform-specific separators, empty identifiers, overlong identifiers, and attachment-directory traversal.
-
