Browse Source

Merge branch 'Net0o-master'

El RIDO 1 week ago
parent
commit
5a44b1e943

+ 3 - 1
.github/copilot-instructions.md

@@ -37,7 +37,8 @@
 ### Build
 
 - **No explicit build step** for PHP. The web app is served directly from source.
-  - This means **composer directory** need to be comitted (_except_ of big optional dependences like Google Cloud like GCS support or similar!)
+  - This means **composer directory** need to be committed (_except_ of big optional dependences like Google Cloud like GCS support or similar!)
+  - But if you do add new PHP classes, you must run `make composer` to get the class added to the auto loader.
 - **For JavaScript:** There is no webpack/bundler step for release; browser JS is written in compatible ES6+ syntax, except in `legacy.js` (which must be designed to run cleanly even on ancient IE4 or Netscape to display the error message that a browser upgrade is necessary). We dropped use of jQuery. We are considering modularizing the JS logic, but need to ensure to do so in a way that will work both in the browser as well as for node JS driven unit tests.
 
 ### Run
@@ -117,3 +118,4 @@
 - **Always run `composer install` before PHP tests, and `npm install` before JS tests.**
 - **Validate all changes by running both PHP and JS tests.**
 - **Review `.github/workflows/` for the latest validation pipeline steps.**
+- Do update the CHANGELOG.md with an ADDED, CHANGED or FIXED list item to the not yet released version on top. New lines get added below the last line that matches the type of your change, but don't mix the types. See earlier releases for guidance on order and wording. If your change addresses an issue, do include the issue ID in brackets at the end. The lines should be short, details are explained in the pull request.

+ 1 - 0
CHANGELOG.md

@@ -1,6 +1,7 @@
 # PrivateBin version history
 
 ## 2.1.0 (not yet released)
+* ADDED: Added `shortenviachhoto` endpoint with an `chhoto` configuration section
 * CHANGED: We removed jQuery in the Frontend and replaced it with vanilla JS.
 * CHANGED: Removed the unmaintained js-verify and replaced it with fast-check library.
 * CHANGED: Added a `jsconfig.json` in order to check the types of JavaScript.

+ 11 - 0
cfg/conf.sample.php

@@ -266,6 +266,17 @@ dir = PATH "data"
 ;version = "latest"
 ;bucket = "my-bucket"
 
+;[chhoto]
+; - Chhoto requires you to make a post call with a generated API key.
+;   use this section to setup the API key and URL. In order to use this section,
+;   "urlshortener" needs to point to the base URL of your PrivateBin
+;   instance with "?shortenviachhoto&link=" appended. For example:
+;   urlshortener = "${basepath}?shortenviachhoto&link="
+;    This URL will in turn call Chhoto on the server side, using the URL from
+;   "apiurl" and the API Key from the "apikey" parameters below.
+; apiurl = "https://chhoto-url.example.com/api/new"
+; apikey = "your_api_key"
+
 ;[shlink]
 ; - Shlink requires you to make a post call with a generated API key.
 ;   use this section to setup the API key and URL. In order to use this section,

+ 19 - 19
composer.lock

@@ -890,16 +890,16 @@
         },
         {
             "name": "phpunit/phpunit",
-            "version": "9.6.35",
+            "version": "9.6.36",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "0edba2f3a0c48df3553cb9b640810b30df60302b"
+                "reference": "abab27ed286d3e1246fbbfe6b56bfd732d945ec9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0edba2f3a0c48df3553cb9b640810b30df60302b",
-                "reference": "0edba2f3a0c48df3553cb9b640810b30df60302b",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/abab27ed286d3e1246fbbfe6b56bfd732d945ec9",
+                "reference": "abab27ed286d3e1246fbbfe6b56bfd732d945ec9",
                 "shasum": ""
             },
             "require": {
@@ -924,7 +924,7 @@
                 "sebastian/comparator": "^4.0.10",
                 "sebastian/diff": "^4.0.6",
                 "sebastian/environment": "^5.1.5",
-                "sebastian/exporter": "^4.0.8",
+                "sebastian/exporter": "^4.0.9",
                 "sebastian/global-state": "^5.0.8",
                 "sebastian/object-enumerator": "^4.0.4",
                 "sebastian/resource-operations": "^3.0.4",
@@ -973,7 +973,7 @@
             "support": {
                 "issues": "https://github.com/sebastianbergmann/phpunit/issues",
                 "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
-                "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.35"
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.36"
             },
             "funding": [
                 {
@@ -981,7 +981,7 @@
                     "type": "other"
                 }
             ],
-            "time": "2026-07-06T14:48:07+00:00"
+            "time": "2026-08-11T06:25:15+00:00"
         },
         {
             "name": "sebastian/cli-parser",
@@ -1424,16 +1424,16 @@
         },
         {
             "name": "sebastian/exporter",
-            "version": "4.0.8",
+            "version": "4.0.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/exporter.git",
-                "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c"
+                "reference": "4352c1a3df741a7ba9e61af6fed51d1fee41cbf7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c",
-                "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/4352c1a3df741a7ba9e61af6fed51d1fee41cbf7",
+                "reference": "4352c1a3df741a7ba9e61af6fed51d1fee41cbf7",
                 "shasum": ""
             },
             "require": {
@@ -1489,7 +1489,7 @@
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/exporter/issues",
-                "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8"
+                "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.9"
             },
             "funding": [
                 {
@@ -1509,7 +1509,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-09-24T06:03:27+00:00"
+            "time": "2026-08-11T04:55:59+00:00"
         },
         {
             "name": "sebastian/global-state",
@@ -1758,16 +1758,16 @@
         },
         {
             "name": "sebastian/recursion-context",
-            "version": "4.0.6",
+            "version": "4.0.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/recursion-context.git",
-                "reference": "539c6691e0623af6dc6f9c20384c120f963465a0"
+                "reference": "c85be6922b7fd365942b986b9a50397d65407611"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0",
-                "reference": "539c6691e0623af6dc6f9c20384c120f963465a0",
+                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/c85be6922b7fd365942b986b9a50397d65407611",
+                "reference": "c85be6922b7fd365942b986b9a50397d65407611",
                 "shasum": ""
             },
             "require": {
@@ -1809,7 +1809,7 @@
             "homepage": "https://github.com/sebastianbergmann/recursion-context",
             "support": {
                 "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
-                "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6"
+                "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.7"
             },
             "funding": [
                 {
@@ -1829,7 +1829,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2025-08-10T06:57:39+00:00"
+            "time": "2026-08-11T05:25:24+00:00"
         },
         {
             "name": "sebastian/resource-operations",

+ 4 - 0
lib/Configuration.php

@@ -112,6 +112,10 @@ class Configuration
             'apikey'    => '',
             'apiurl'    => '',
         ],
+        'chhoto' => [
+            'apikey'    => '',
+            'apiurl'    => '',
+        ],
         // update this array when adding/changing/removing js files
         'sri' => [
             'js/base-x-5.0.1.js'     => 'sha512-FmhlnjIxQyxkkxQmzf0l6IRGsGbgyCdgqPxypFsEtHMF1naRqaLLo6mcyN5rEaT16nKx1PeJ4g7+07D6gnk/Tg==',

+ 5 - 1
lib/Controller.php

@@ -16,6 +16,7 @@ use PrivateBin\Exception\TranslatedException;
 use PrivateBin\Persistence\ServerSalt;
 use PrivateBin\Persistence\TrafficLimiter;
 use PrivateBin\Proxy\AbstractProxy;
+use PrivateBin\Proxy\ChhotoProxy;
 use PrivateBin\Proxy\ShlinkProxy;
 use PrivateBin\Proxy\YourlsProxy;
 
@@ -158,6 +159,9 @@ class Controller
             case 'shlinkproxy':
                 $this->_shortenerproxy(new ShlinkProxy($this->_conf, $this->_request->getParam('link')));
                 break;
+            case 'chhotoproxy':
+                $this->_shortenerproxy(new ChhotoProxy($this->_conf, $this->_request->getParam('link')));
+                break;
         }
 
         $this->_setCacheHeaders();
@@ -454,7 +458,7 @@ class Controller
         $page->assign('CSPHEADER', $metacspheader);
         $page->assign('ERROR', I18n::_($this->_error));
         $page->assign('NAME', $this->_conf->getKey('name'));
-        if (in_array($this->_request->getOperation(), ['shlinkproxy', 'yourlsproxy'], true)) {
+        if (in_array($this->_request->getOperation(), ['shlinkproxy', 'yourlsproxy', 'chhotoproxy'], true)) {
             $page->assign('SHORTURL', $this->_status);
             $page->draw('shortenerproxy');
             return;

+ 11 - 3
lib/Proxy/AbstractProxy.php

@@ -30,6 +30,14 @@ abstract class AbstractProxy
      */
     private $_error = '';
 
+    /**
+     * proxy URL
+     *
+     * @access protected
+     * @var    string
+     */
+    protected $_proxyUrl = '';
+
     /**
      * shortened URL
      *
@@ -61,15 +69,15 @@ abstract class AbstractProxy
             return;
         }
 
-        $proxyUrl = $this->_getProxyUrl($conf);
+        $this->_proxyUrl = $this->_getProxyUrl($conf);
 
-        if (empty($proxyUrl)) {
+        if (empty($this->_proxyUrl)) {
             $this->_error = 'Proxy error: Proxy URL is empty. This can be a configuration issue, like wrong or missing config keys.';
             $this->logErrorWithClassName($this->_error);
             return;
         }
 
-        $data = file_get_contents($proxyUrl, false,
+        $data = file_get_contents($this->_proxyUrl, false,
             stream_context_create(
                 [
                     'http' => $this->_getProxyPayload($conf, $link),

+ 90 - 0
lib/Proxy/ChhotoProxy.php

@@ -0,0 +1,90 @@
+<?php declare(strict_types=1);
+/**
+ * PrivateBin
+ *
+ * a zero-knowledge paste bin
+ *
+ * @link      https://github.com/PrivateBin/PrivateBin
+ * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
+ * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
+ */
+
+namespace PrivateBin\Proxy;
+
+use JsonException;
+use PrivateBin\Configuration;
+use PrivateBin\Json;
+
+/**
+ * ChhotoProxy
+ *
+ * Forwards a URL for shortening to Chhoto URL and stores the result.
+ */
+class ChhotoProxy extends AbstractProxy
+{
+    /**
+     * Overrides the abstract parent function to get the proxy URL.
+     *
+     * @param Configuration $conf
+     * @return string
+     */
+    protected function _getProxyUrl(Configuration $conf): string
+    {
+        return $conf->getKey('apiurl', 'chhoto');
+    }
+
+    /**
+     * Overrides the abstract parent function to get contents from Chhoto API.
+     *
+     * @access protected
+     * @param Configuration $conf
+     * @param string $link
+     * @return array
+     */
+    protected function _getProxyPayload(Configuration $conf, string $link): array
+    {
+        $apiKey = $conf->getKey('apikey', 'chhoto');
+
+        $body = [
+            'shortlink'    => '',          // empty = auto-generate
+            'longlink'     => $link,
+            'expiry_delay' => 0,           // 0 = never expire
+            'notes'        => 'PrivateBin paste',
+        ];
+
+        try {
+            return [
+                'method'  => 'POST',
+                'header'  => "Content-Type: application/json\r\n" .
+                             'X-API-Key: ' . $apiKey . "\r\n" .
+                             "Accept: application/json\r\n",
+                'content' => Json::encode($body),
+            ];
+        } catch (JsonException $e) {
+            error_log('[' . get_class($this) . '] Error encoding body: ' . $e->getMessage());
+            return [];
+        }
+    }
+
+    /**
+     * Extracts the short URL from the Chhoto API response.
+     *
+     * @access protected
+     * @param array $data
+     * @return ?string
+     */
+    protected function _extractShortUrl(array $data): ?string
+    {
+        // Chhoto usually returns "shorturl"
+        if (!empty($data['shorturl'])) {
+            return $data['shorturl'];
+        }
+
+        // Fallback for older versions that return only the slug
+        if (!empty($data['shortlink'])) {
+            return $this->_proxyUrl . ltrim($data['shortlink'], '/');
+        }
+
+        return null;
+    }
+}

+ 4 - 0
lib/Request.php

@@ -130,6 +130,7 @@ class Request
                     'pasteid'          => FILTER_SANITIZE_SPECIAL_CHARS,
                     'shortenviayourls' => FILTER_SANITIZE_SPECIAL_CHARS,
                     'shortenviashlink' => FILTER_SANITIZE_SPECIAL_CHARS,
+                    'shortenviachhoto' => FILTER_SANITIZE_SPECIAL_CHARS,
                 ], false);
         }
         if (
@@ -158,6 +159,9 @@ class Request
             if (str_contains($this->getRequestUri(), '/shortenviashlink') || array_key_exists('shortenviashlink', $this->_params)) {
                 $this->_operation = 'shlinkproxy';
             }
+            if (str_contains($this->getRequestUri(), '/shortenviachhoto') || array_key_exists('shortenviachhoto', $this->_params)) {
+                $this->_operation = 'chhotoproxy';
+            }
         }
     }
 

+ 175 - 0
tst/Proxy/ChhotoProxyTest.php

@@ -0,0 +1,175 @@
+<?php declare(strict_types=1);
+
+use PHPUnit\Framework\TestCase;
+use PrivateBin\Configuration;
+use PrivateBin\Proxy\ChhotoProxy;
+
+class ChhotoProxyTest extends TestCase
+{
+    private $_conf;
+
+    private $_path;
+
+    private $_mock_chhoto_service;
+
+    public function setUp(): void
+    {
+        /* Setup Routine */
+        $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
+        if (!is_dir($this->_path)) {
+            mkdir($this->_path);
+        }
+        $this->_mock_chhoto_service           = $this->_path . DIRECTORY_SEPARATOR . 'chhoto.json';
+        $options                              = parse_ini_file(CONF_SAMPLE, true);
+        $options['main']['basepath']          = 'https://example.com/';
+        $options['main']['urlshortener']      = 'https://example.com/shortenviachhoto?link=';
+        $options['chhoto']['apiurl']          = $this->_mock_chhoto_service;
+        $options['chhoto']['apikey']          = 'test_api_key';
+        Helper::confBackup();
+        Helper::createIniFile(CONF, $options);
+        $this->_conf = new Configuration;
+    }
+
+    public function tearDown(): void
+    {
+        /* Tear Down Routine */
+        unlink(CONF);
+        Helper::confRestore();
+        Helper::rmDir($this->_path);
+    }
+
+    public function testChhotoProxy()
+    {
+        // Chhoto usually returns the full short URL in the "shorturl" field.
+        file_put_contents($this->_mock_chhoto_service, '{"shorturl":"https:\/\/lix.sk\/abc123"}');
+
+        $chhoto = new ChhotoProxy($this->_conf, 'https://example.com/?foo#bar');
+        $this->assertFalse($chhoto->isError());
+        $this->assertEquals($chhoto->getUrl(), 'https://lix.sk/abc123');
+
+        // A URL that embeds a foreign host in the user-info part must still be
+        // accepted, because the host of the link is our own instance.
+        $chhoto = new ChhotoProxy($this->_conf, 'https://example.com/?@foreign.malicious.example?foo#bar');
+        $this->assertFalse($chhoto->isError());
+        $this->assertEquals($chhoto->getUrl(), 'https://lix.sk/abc123');
+    }
+
+    public function testChhotoProxyWithSlugFallback()
+    {
+        // Older Chhoto versions may return only the slug in "shortlink" instead
+        // of a full "shorturl". The proxy then prepends the configured apiurl.
+        file_put_contents($this->_mock_chhoto_service, '{"shortlink":"abc123"}');
+
+        $chhoto = new ChhotoProxy($this->_conf, 'https://example.com/?foo#bar');
+        $this->assertFalse($chhoto->isError());
+        // apiurl (the mock file path) is prepended to the ltrimmed slug.
+        $this->assertEquals($chhoto->getUrl(), $this->_mock_chhoto_service . 'abc123');
+    }
+
+    public function testChhotoProxyWithLeadingSlashSlug()
+    {
+        // A slug that already starts with a slash must not produce a double slash.
+        file_put_contents($this->_mock_chhoto_service, '{"shortlink":"/abc123"}');
+
+        $chhoto = new ChhotoProxy($this->_conf, 'https://example.com/?foo#bar');
+        $this->assertFalse($chhoto->isError());
+        $this->assertEquals($chhoto->getUrl(), $this->_mock_chhoto_service . 'abc123');
+    }
+
+    /**
+     * @dataProvider providerInvalidUrl
+     */
+    public function testInvalidUrl($url): void
+    {
+        $chhoto = new ChhotoProxy($this->_conf, $url);
+        $this->assertTrue($chhoto->isError());
+        $this->assertEquals($chhoto->getError(), 'Invalid URL given.');
+    }
+
+    public function providerInvalidUrl(): array
+    {
+        return [
+            [''],
+            [' '],
+            ['foo'],
+            ['https://'],
+            ['https://example.com'], // missing path and query parameter,
+            ['https://example.com/'], // missing query parameter
+            ['https://example.com?paste=something'], // missing path parameter
+            ['https://example.com@foreign.malicious.example?foo#bar'], // missing path parameter
+        ];
+    }
+
+    /**
+     * This tests for a trick using username of an URI, see:
+     * {@see https://cloud.google.com/blog/topics/threat-intelligence/url-obfuscation-schema-abuse/?hl=en}
+     *
+     * @dataProvider providerForeignUrlUsernameTrick
+     */
+    public function testForeignUrlUsingUsernameTrick($url): void
+    {
+        $chhoto = new ChhotoProxy($this->_conf, $url);
+        $this->assertTrue($chhoto->isError());
+        $this->assertEquals($chhoto->getError(), 'Trying to shorten a URL that isn\'t pointing at our instance.');
+    }
+
+    public function providerForeignUrlUsernameTrick(): array
+    {
+        return [
+            ['https://example.com@foreign.malicious.example/?foo#bar'],
+            ['https://example.com/@foreign.malicious.example?foo#bar'],
+        ];
+    }
+
+    /**
+     * @dataProvider providerForeignUrl
+     */
+    public function testForeignUrl($url): void
+    {
+        $chhoto = new ChhotoProxy($this->_conf, $url);
+        $this->assertTrue($chhoto->isError());
+        $this->assertEquals($chhoto->getError(), 'Trying to shorten a URL that isn\'t pointing at our instance.');
+    }
+
+    public function providerForeignUrl(): array
+    {
+        return [
+            ['ftp://example.com/?n=np'], // wrong protocol
+            ['https://other.example.com/?foo#bar'], // wrong domain
+            ['https://other.example.com/?q=https://example.com/?foo#bar'], // domain included inside string
+        ];
+    }
+
+    public function testChhotoError()
+    {
+        // Chhoto may reply with a body that contains neither "shorturl" nor
+        // "shortlink"; this must be handled gracefully as an error instead of
+        // raising a TypeError (the method is declared to return ?string).
+        file_put_contents($this->_mock_chhoto_service, '{"message":"error"}');
+
+        $chhoto = new ChhotoProxy($this->_conf, 'https://example.com/?foo#bar');
+        $this->assertTrue($chhoto->isError());
+        $this->assertEquals($chhoto->getError(), 'Proxy error: Error parsing proxy response. This can be a configuration issue, like wrong or missing config keys.');
+    }
+
+    public function testChhotoSuccessWithoutShortUrl()
+    {
+        // A 200-style reply that omits the short URL fields must be treated as
+        // an error, not as a successful (empty) shortening.
+        file_put_contents($this->_mock_chhoto_service, '{"status":"ok"}');
+
+        $chhoto = new ChhotoProxy($this->_conf, 'https://example.com/?foo#bar');
+        $this->assertTrue($chhoto->isError());
+        $this->assertEquals($chhoto->getError(), 'Proxy error: Error parsing proxy response. This can be a configuration issue, like wrong or missing config keys.');
+    }
+
+    public function testServerError()
+    {
+        // simulate some other server error that results in a non-JSON reply
+        file_put_contents($this->_mock_chhoto_service, '500 Internal Server Error');
+
+        $chhoto = new ChhotoProxy($this->_conf, 'https://example.com/?foo#bar');
+        $this->assertTrue($chhoto->isError());
+        $this->assertEquals($chhoto->getError(), 'Proxy error: Error parsing proxy response. This can be a configuration issue, like wrong or missing config keys.');
+    }
+}

+ 0 - 0
tst/YourlsProxyTest.php → tst/Proxy/YourlsProxyTest.php


+ 1 - 0
vendor/composer/autoload_classmap.php

@@ -88,6 +88,7 @@ return array(
     'PrivateBin\\Persistence\\ServerSalt' => $baseDir . '/lib/Persistence/ServerSalt.php',
     'PrivateBin\\Persistence\\TrafficLimiter' => $baseDir . '/lib/Persistence/TrafficLimiter.php',
     'PrivateBin\\Proxy\\AbstractProxy' => $baseDir . '/lib/Proxy/AbstractProxy.php',
+    'PrivateBin\\Proxy\\ChhotoProxy' => $baseDir . '/lib/Proxy/ChhotoProxy.php',
     'PrivateBin\\Proxy\\ShlinkProxy' => $baseDir . '/lib/Proxy/ShlinkProxy.php',
     'PrivateBin\\Proxy\\YourlsProxy' => $baseDir . '/lib/Proxy/YourlsProxy.php',
     'PrivateBin\\Request' => $baseDir . '/lib/Request.php',

+ 1 - 0
vendor/composer/autoload_static.php

@@ -136,6 +136,7 @@ class ComposerStaticInitDontChange
         'PrivateBin\\Persistence\\ServerSalt' => __DIR__ . '/../..' . '/lib/Persistence/ServerSalt.php',
         'PrivateBin\\Persistence\\TrafficLimiter' => __DIR__ . '/../..' . '/lib/Persistence/TrafficLimiter.php',
         'PrivateBin\\Proxy\\AbstractProxy' => __DIR__ . '/../..' . '/lib/Proxy/AbstractProxy.php',
+        'PrivateBin\\Proxy\\ChhotoProxy' => __DIR__ . '/../..' . '/lib/Proxy/ChhotoProxy.php',
         'PrivateBin\\Proxy\\ShlinkProxy' => __DIR__ . '/../..' . '/lib/Proxy/ShlinkProxy.php',
         'PrivateBin\\Proxy\\YourlsProxy' => __DIR__ . '/../..' . '/lib/Proxy/YourlsProxy.php',
         'PrivateBin\\Request' => __DIR__ . '/../..' . '/lib/Request.php',

+ 2 - 2
vendor/composer/installed.php

@@ -3,7 +3,7 @@
         'name' => 'privatebin/privatebin',
         'pretty_version' => 'dev-master',
         'version' => 'dev-master',
-        'reference' => '8a77305838325d7be15b9a96e43415a6d2796489',
+        'reference' => 'cdfce1701ec2b2c197bd1c43c72e79b21339b8ff',
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -31,7 +31,7 @@
         'privatebin/privatebin' => array(
             'pretty_version' => 'dev-master',
             'version' => 'dev-master',
-            'reference' => '8a77305838325d7be15b9a96e43415a6d2796489',
+            'reference' => 'cdfce1701ec2b2c197bd1c43c72e79b21339b8ff',
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),